During the process of writing Node.js, continuous IO operations may lead to "pyramid nightmare". The multiple nesting of callback functions makes the code difficult to maintain. CommonJs' Promise is used to encapsulate asynchronous functions and use a unified chain API to get rid of the nightmare of multiple callbacks.
The non-blocking IO model provided by Node.js allows us to use callback functions to handle IO operations, but when continuous IO operations are required, your callback functions will be nested multiple times, the code is not beautiful, and it is not easy to maintain, and there may be many repeated codes for error handling, which is the so-called "Pyramid of Doom".
The code copy is as follows:
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
This is actually the problem of Control flow in Node.js. There are many solutions to this problem, such as using async, eventProxy, etc. However, the topic of this article is to use Promise in the CommonJs specification to solve this problem.
What is Promise?
There are many types of Promise specifications for CommonJs. We generally discuss the Promise/A+ specification, which defines the basic behavior of Promise.
A Promise is an object that usually represents an asynchronous operation that may be completed in the future. This operation may succeed or fail, so a Promise object generally has 3 states: Pending, Fulfilled, and Rejected. It represents unfinished, successful completion and operation failure, respectively. Once the state of the Promise object changes from Pending to either Fulfilled or Rejected, its state cannot be changed again.
A Promise object usually has a then method, which allows us to operate the value returned after possible success in the future or the reason for failure. This then method looks like this:
promise.then(onFulfilled, onRejected)
It is obvious that the then method accepts two parameters, which are usually two functions, one is used to process the result after the operation is successful, and the other is used to process the cause of the operation failure. The first parameters of these two functions are the result after the success and the cause of the failure. If the then method is not a function, then this parameter will be ignored.
The return value of the then method is a Promise object, which allows us to chain call then to achieve the effect of controlling the process. There are many details here, such as value transfer or error handling. The Promise specification is defined like this:
The return value of the onFulfilled or onRejected function is not a Promise object, then the value will be used as the first parameter of onFulfilled in the next then method. If the return value is a Promise object, how can the return value of the then method be the Promise object
If an exception is thrown in the onFulfilled or onRejected function, the status of the then method returned Promise object is converted to Rejected. If the Promise object calls then, the Error object will be used as the first parameter of the onRejected function.
If the Promise state becomes Fulfilled and the onFulfilled function is not provided in the then method, the Promise object state returned by the then method becomes Fulfilled and the successful result is the result of the previous Promise, the same is true for Rejected.
To add, both onFulfilled and onRejected are executed asynchronously.
Implementation of specification: q
The above is about the specification of Promise, and what we need is its implementation. q is a library that has better implementation specifications for Promise/A+.
First of all, we need to create a Promise object. The specifications for the creation of Promise object are in Promise/B. I will not explain in detail here, just add the code.
The code copy is as follows:
function(flag){
var defer = q.defer();
fs.readFile("a.txt", function(err, data){
if(err) defer.reject(err);
else defer.resolve(data);
});
return defer.promise;
}
Most Promise implementations are similar in the creation of Promise. By creating a defer object with promise attribute, if the value is successfully obtained, defer.resolve(value) is called, if it fails, defer.reject(reason) is called, and finally return the promise attribute of defer. This process can be understood as calling defer.resolve turns the state of the Promise into Fulfilled, and calling defer.reject turns the state of the Promise into Rejected.
When facing a series of continuous asynchronous methods, how can you write beautiful code using Promise? Take a look at the following example.
The code copy is as follows:
promise0.then(function(result){
// dosomething
return result;
}).then(function(result) {
// dosomething
return promise1;
}).then(function(result) {
// dosomething
}).catch(function(ex) {
console.log(ex);
}). finally(function(){
console.log("final");
});
In the above code, the then method only accepts OnFulfilled, and the catch method is actually then(null, OnRejected). In this way, as long as a series of asynchronous methods always return values successfully, the code will run downward in a waterfall style. If any of the asynchronous methods fail or an exception occurs, then according to the CommonJs Promise specification, the function in the catch will be executed. q also provides finally method, which is easy to understand literally, that is, whether it resolves or rejects, the function in finally will be executed.
It looks good, the code is more maintained and beautiful, so what if you want concurrency?
The code copy is as follows:
q.all([promise0, promise1, promise2]).spread(function(val0, val1, val2){
console.log(arguments);
}).then(function(){
console.log("done");
}).catch(function(err){
console.log(err);
});
q also provides an API for concurrency, calling all methods and passing a Promise array can continue to use then's chain style. There are also good things like q.nfbind, etc. that can convert Node.js' native API into Promise to unify the code format. More APIs will not be described in detail here.
in conclusion
This article mainly introduces the use of Promise to solve the Node.js control flow problem, but Promise can also be applied to the front-end. EMCAScript6 has provided native API support. It should be pointed out that Promise is not the only solution, async is also a good choice and provides a more friendly concurrency control API, but I think Promise has more advantages when encapsulating functions with asynchronous methods.
Okay, that's all for this article, I hope it will be helpful to everyone.