Original address cryto.net/~joepie91/b…

One topic that often bothers people in the # node. js# channel is Bluebird’s promise.try method. You don’t really know what this method does or why you’re using it. At the same time, almost all Promsie guides demonstrate that this method is wrong and the situation does not improve.

In this article, I’ll try to explain exactly what promise.try is and why you should use it. I’m going to assume that you already know something about promises and that you know how. Then works in promises.

Even if you’re using a different Promsie implementation (such as ES6 Promise), this article can help you. At the end of the article I explain how to implement the same functionality in a non-Bluebird environment.

What exactly is promise.try?

In simple terms, promise.try is a lot like.then, except that it doesn’t need to be followed by a pre-promise. That still leaves some ambiguity, so let’s look at an example.

Here’s a typical Promise usage scenario:

	
functiongetUsername(userId){

returndatabase.users.get({id:userId})

.then(function(user){

returnuser.name;

});Copy the code

So far, so good. Let’s assume that database.users.get returns a Promise, and that the Promise will eventually return an object with a name attribute.

Here is the same code, but with promise.try introduced:

	
varPromise=require("bluebird");

functiongetUsername(userId){

returnPromise.try(function(){

returndatabase.users.get({id:userID});

}).then(function(user){

returnuser.name;

});Copy the code

As you can see, our call chain starts with promise.try instead of database.users.get. As with.then, we execute the promise.try method and pass it a function that returns the database.users.get call directly.

What’s the point of doing that?

The above code may seem redundant. But it actually has the following advantages:

  1. Better error handling an exception in synchronized code is delivered to the back end of the Promise chain as a Rejection wherever it occurs.
  2. Better compatibility You can always use your own preferred Promise implementation without worrying about which one third-party code is using.
  3. Better code reading Experience All code will be at the same level of indentation horizontally, which makes it easier to read code.

I’m going to go through each of them:

1. Better error handling

A much-touted advantage of promises is that the user can handle both synchronous and asynchronous exceptions in the same way — the synchronous exception is caught and passed back as a Rejected Promise. But is this really the case? Let’s look at the following small variation of the previous example:

	
functiongetUsername(userId){

returndatabase.users.get({id:userId})

.then(function(user){

returnuesr.name;

});Copy the code

In this modified version, we mistakenly typed user.name, which is now uesr.name. Because uESR is undefined, it cannot have any attributes, so this line of code raises an exception. Then, as you might expect, the exception is caught by.then and turned into a Rejected Promise.

But what if database.users.get synchronization throws an exception? What if there are typos or other problems in third-party code? Promises’ bug catching mechanism requires that all synchronous code written by the user be placed in a. Then so that it can be executed in a try/catch block.

But… Database.users.get in our code is not in the.then block. So promises can’t access that part of the code and they can’t wrap it around a try/catch. This results in synchronous exceptions that cannot be turned into asynchronous exceptions. We’re back to square one, having to deal with two types of exceptions — synchronous and asynchronous.

Now, let’s go back to an example that uses promise.try:

varPromise=require("bluebird");

functiongetUsername(userId){

returnPromise.try(function(){

returndatabase.users.get({id:userID});

}).then(function(user){

returnuser.name;

});Copy the code

I said Promise. Try is like.then, but it doesn’t have to come after Promise. In addition, it catches synchronization exceptions in database.users.get, just like.then!

By introducing promise.try, we’ve changed the error handling of our code to cover all synchronous exceptions, rather than catching only exceptions after the first asynchronous operation (the first.then call).

Promises/A+

Before we move on to the next benefits of promise.try, let’s look at Promises/A+ and what role it can play. Promises/A+ says about Promises:

An open Standard for Sound, Interoperable JavaScript Promises — by Implementers, for implementers.

In other words, it is a promise that Promises different Promises (Bluebird,ES6,Q,RSVP…) Specifications that can be seamlessly connected. Promises/A+ specification is why you can choose any Promise implementation you like and not have to worry about what Promise implementation A third party code base like Knex will use.

Here is how Promises/A+ will help users:

All red-highlighted functions in the example return Bluebird Promises, blue highlighted returns ES6 Promise, and green highlighted returns Q Promise.

Note that even if we return an ES6 Promise in the first callback, the enclosing.then method will return a Bluebird Promise. Similarly, our doStuff function returns a Bluebird Promise even though the second callback returns a Q Promise.

The reason for this is that, in addition to catching synchronization exceptions,.then also processes the return value of the callback function to return a Promise object from the same implementation as.then itself. In practice, this means that the first Promise in the invocation chain determines which Promise implementation you will work with in subsequent code.

This is a practical way to ensure API consistency. All you need to worry about is the Promise implementation used by the first method in the call chain, not the implementation used by the subsequent code.

2. Better compatibility

But the behavior described above does not always meet expectations. Let’s take a look at the following example:

varPromise=require("bluebird");

functiongetAllUsernames(){

// This will return an ES6 Promise.

returndatabase.users.getAll()

.map(function(user){

returnuser.name;

});Copy the code

If you are not familiar with Map and want to learn more, please refer to this article.

The.map method used in this example is a Bluebird proprietary feature that is not available on ES6 Promises. Since the first function in the call chain (database.users.getall) returns an ES6 Promise, we can’t access this method here. Even if Bluebird is used elsewhere in the project.

Now let’s look at the example again, only this time we use promise.try:

varPromise=require("bluebird");

functiongetAllUsernames(){

// This will return a Bluebird Promise.

returnPromise.try(function(){

// This will return an ES6 Promise.

returndatabase.users.getAll();

}).map(function(user){

returnuser.name;

});Copy the code

Now we can use.map! Because we started the invocation chain with the Promise. Try from Bluebird, all subsequent Promises became Bluebird Promises. So we don’t have to worry about what happens in every dot then callback function.

By introducing promise.try like this, you can decide which implemented Promise to use throughout the call chain, as long as the first Promise returned in the call chain is the type you want. You can’t achieve the same effect with.then, because the.then needs to be followed by a pre-promise, and often you can’t decide what kind of Promise the pre-promise comes from.

3. Better code reading experience

The final advantage has to do with readability. By opening each invocation chain with promise.try, all of the “business logic” appears horizontally on the same indentation level. While this advantage may seem trivial, it can actually make a big difference because of the way humans scan large chunks of text.

To illustrate the difference, here’s how you can read the code without promise.try:

… Then we have the same code, but with promise.try:

While this makes the code look slightly “complicated,” it helps you understand the main logic of the code faster because your eyes no longer have to look left and right for indentation.

What if I’m not using Bluebird?

As far as I know, Bluebird is currently the only Promise implementation that comes with the promise.try method. However, copying this functionality to other Promise implementations is fairly easy, as long as the new Promise of this Promise implementation catches synchronization exceptions.

For example, ES6-Promise-try is an implementation I provided for ES6 Promises that also works in browsers. I haven’t had a chance to document it yet. But it is used essentially the same way as promise.try, with the method name changed to promiseTry, as shown in the following example:

	
varpromiseTry=require("es6-promise-try");

functiongetUsername(userId){

returnpromiseTry(function(){

returndatabase.users.get({id:userID});

}).then(function(user){

returnuser.name;

});Copy the code

Note that es6-promise-try defaults to the assumption that your runtime supports ES6 Promises. If not, you’ll need to introduce polyfill scripts like ES6-Promise.

Because Bluebird has robust error handling and strong debugging capabilities, I strongly recommend that you give it a high priority. In general, ES6 Promises will only be your first choice in limited environments, such as requiring compatibility with older Versions of Internet Explorer or trying to reduce the front end file size.

If you know of any other Promise libraries that implement promise.try, please contact me. I’ll list them here. My contact information can be found at the bottom of the page.

I am currently providing Node.js code review and coaching services. For more information click here