days since this article was written, please be aware of its timeliness
Recently, while studying topics related to Promise, I came across this article and found it quite insightful, so I decided to document it.
Promises itself is quite simple—provided you can find the right approach. Below are a few confusing aspects of Promises to test whether you’ve truly mastered Promise. Some of these once drove me crazy.
Nested Promises
You have a bunch of Promises nested within each other:
1 | |
The reason you’re doing this is that you need to handle the results of both Promises, so you can’t chain them because the then() method only accepts the result returned by the previous then(). (This means these two Promise need to be processed simultaneously without any sequential dependency, whereas then implies a sequential relationship. If the former throw error, it directly enters the catch handling phase.)
Hah, the real reason you wrote it this way is that you didn’t know about the all() method:
Here’s the solution to this ugly approach:
1 | |
Much cleaner. q.all() returns a promise object, combines the results into an array, and passes it to the resolve method for subsequent then method calls. The spread() method will split this array into arguments of the same length and pass them to the DoSomethingOnThem function.
(Note: Promise.all() accepts an array as a parameter, where the elements are promise and have no sequential dependency. They execute simultaneously, and the final value passed to the then method is an array of the return values from each promise method. Here, the author uses a module q from node as an example.)
Broken Promise Chain
Suppose you have code like this:
1 | |
The problem with this code is that any errors occurring in the somethingComplicated() function’s error won’t be caught. Promises implies the ability to chain calls (otherwise, why call it then? Just use done directly). Each called then() method returns a new promise, and this new promise will be processed by the next then() method. Normally, the last call should be the catch() method, and any error occurring anywhere in the chain will be caught and handled by it.
In the above code, the chain breaks when you return the first promise instead of returning a new promise processed by then for the final then call (i.e., then doesn’t modify the original promise; it only processes it and returns a new promise).
The solution to this problem:
1 | |
Remember: always return the result of the last then() (to enable chaining).
Chaotic Collection
You have an array of elements, and you want to perform some asynchronous operation on each element. So you find yourself needing to do something involving recursive calls.
1 | |
Well… this code isn’t very intuitive. The crux of the problem is that chaining becomes painful when you don’t know how long the chain will be—unless you know about (JavaScript ES5+’s native array methods) map() and reduce().
Solution:
Remember, the q.all parameter is an array of promise, and it will place the results into an array and pass them to the resolve method. We can simply use the array’s map method to perform this asynchronous operation on each element, like this:
1 | |
Unlike the initial recursive approach, which wasn’t really a solution, this code synchronously passes each array element to an asynchronous function. It’s clearly more time-efficient.
If you need to return promises in order, you can use reduce:
1 | |
Not quite as tidy, but certainly tidier than the original approach.
Ghost Promise
There’s a deterministic method (meaning it’s determined when the Promise starts executing, not based on the result during execution—Translator’s note) that sometimes requires asynchronous execution and sometimes doesn’t. So, to handle both cases, you create a Promise just to maintain consistency between asynchronous and synchronous scenarios (for abstraction and decoupling—Translator’s note), even though only one case might actually occur.
1 | |
The above code isn’t the worst example of an anti-pattern, but it could be written more clearly—by wrapping value or promise with Q(). The Q() method accepts both a value and a promise as arguments:
1 | |
Note: Initially, I suggested using Q.when() for this scenario, but thanks to a comment from Kris Kowal, I was saved from this mistake. Don’t use Q.when(); just use Q(). The latter is much clearer.
Overeager Error Handler
The section title implies simultaneously setting
thenandrejectedinfulfilled, hoping to use therejectedfunction to handle errors infulfilledthat also serve as parameters for thethenfunction. However, this is impossible—theerrorinfulfilledcan only be passed to the nextthen()and cannot be processed by the currentrejectedfunction. Hence, the section title is “Overeager”—it eagerly desires to handle errors, but those errors will never be passed to it for processing. —Translator’s Note
The then() method accepts two parameters: an operation function for the fulfilled state and an operation function for the rejected state. You may have written code like this:
1 | |
I often wish that when facing some key decisions in life, someone could tell me the best course of action so that I would not waste my precious time. Putting myself in others' shoes, I therefore write blogs often, hoping to record in this tiny corner of the vast Internet the once-in-a-lifetime experiences that matter to me, and to help those who seek help.