Asynchronous && Callback
When Node
brings us an asynchronous world, our applications begin to become non-blocking and much faster.
However, it also makes our code complex and unpredictable.
See this simple code:
function readJSON(filename, callback) {
fs.readFile(filename, 'utf8', function (err, res) {
if (err) return callback(err);
try {
res = JSON.parse(res);
} catch (e) {
return callback(e);
}
callback(null, res);
});
}
The code above is somewhat ugly and we actually can't make sure what arguments input to callback, and we must do try-catch for JSON.parse
to handle the error. Luckily, a solution for asynchronous called Promises
help us handle errors naturally and make code clean.
Promises
As its name means, a promise represents the result of an asynchronous operation with three states:
- Pending - The initial state
- Fulfilled - A successful operation
- Rejected - A failed operation
And once a promise is fulfilled or rejected, it is immutable.
Begin Promise
Let's begin with such codes:
var fs = require('fs')
, Promise = require('promise');
function readFile(filename, enc){
return new Promise(function (fulfill, reject){
fs.readFile(filename, enc, function (err, res){
if (err) reject(err);
else fulfill(res);
});
});
}
function readJSON(filename){
return readFile(filename, 'utf8').then(function (res){
console.log(JSON.parse(res));
}, function (err) {
console.log(err);
});
}
As we can see, we re-write our readJSON
function, there is no unpredictable callback and we almost use Promise
to finish our code. In our code, we use new Promise
to construct the promise, which requires a function called resolver, and the resolver function is passed two arguments: resolve and reject.
new Promise(function (resolve, reject) {})
resolve and reject
The resolve and reject should be called with a single argument, and promise will be fulfilled or rejected with that value. A special situation is when resolve is called with a promise(A) then the returned promise takes on the state of that new promise(A).
Things go easily in promise, we only need to pass the value to resolve()
or reject()
to wait for the result and then handle it through then()
. We do not care the asynchronous action's creation time and eventual success or failure, just let asynchronous methods return values like
synchronous methods. So what actually then()
is?
Promise.prototype.then(onFulfilled, onRejected)
This prototype method follows the Promises/A+ spec. Simply to say, it appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler. And it can be chained.
new Promise(function (fulfill, reject){})
.then(onFulfilled, onRejected)
.then(onFulfilled, onRejected)
.then(onFulfilled, onRejected)
// ...
Browser
Actually, there is not only node module promise support Promises
, in some browser, like Chrome 32 and latest Firefox, have already support it. It is similar to what we write before.
if (window.Promise) {
var promise = new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
request.open('GET', 'http://api.icndb.com/jokes/random');
request.onload = function () {
if (request.status == 200) {
resolve(request.response);
} else {
reject(Error(request.statusText));
}
};
request.onerror = function () {
reject(Error('Error fetching data'));
};
request.send();
}).then(function (data) {
console.log(JSON.parse(data).value.joke);
}, function (err) {
console.log(err.message);
})
} else {
console.log('Promise is not supported')
}
More static methods
We have seen how promises help us to do with complex asynchronous code, actually, there are more advanced patterns for promise use and some of the helper methods may make our Promise more concise. I just refer it simply.
- Promise.resolve(value)
- Promise.reject(reason)
- Promise.all(iterable)
- Promise.race(iterable)
If you want to read more, see MDN or Promise/A+.