by Diego Calderón / @codekult
“Many developers are excited by the promise of better async patterns. But it's impossible to effectivily use any abstraction if you don't understand what it's abstracting, and why”
It seems that in JS we can do many things at the same time, make a request, set event listeners or cycle through a loop.
But really the JS engine is single threaded, an have what is called a run-to-completion behavior.
It's often necessary to do some form of interaction coordination between these concurrent "processes", to prevent race conditions or to prevent blocking the event loop.
Also don't forget that this single thread is shared with the browser's tasks, as repainting, updating styles and handling user interaction.
element.addEventListener('click', function () {
// Do something when click
});
They are the most fundamental async pattern in the language, and work in a manner called Continuation Passing Style:
element.addEventListener('click', function () {
setTimeOut(function () {
ajax('/url', function (response) {
console.log(response);
});
}, 500);
});
It seems pretty straightforward, but:
console.log('A');
element.addEventListener('click', function () {
console.log('B');
setTimeOut(function () {
ajax('/url', function (response) {
console.log(response);
});
console.log('C');
}, 500);
console.log('D');
});
console.log('E');
// A, E, B, D, C, response
Other callback withdraws:
Callbacks are the fundamental unit of asynchrony in JS, and the same as CPS or the different concurrency management patterns, they're very useful.
But they're not enough for the evolving landscape of async programming as JS matures.
Promises are a software abstraction that represent (or are the placeholder for) the eventual result of an asynchronous operation.
then
method whose behavior conforms to
this specification.undefined
, a thenable, or a promise.then
methodIt's at the core of the specification and provides a shared common base to all implementations.
Also allows Promises/A+ implementations to "assimilate" nonconformant implementations with reasonable then methods.
Q - Complete and Powerful. Used in Angular.js and written by one of the A+ spec's author.
rsvp.js - Simple but lightweight. There is a polyfill based on a subset of this library.
jQuery - A+ non-conformant.
Node.js and io.js have full support.
http://kangax.github.io/compat-table/es6/
var myPromise = new Promise(function (resolve, reject) {
// Do a thing, possibly async, then…
if (/* success */) {
resolve(value);
} else {
reject(reason); // It's recommended to use an `Error` object as reason.
}
});
myPromise.then(
function (value) { /* fulfillment */ },
function (reason) { /* rejection */ }
);
Promisifying XMLHttpRequest
function httpGet(url) {
return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (this.status === 200) {
// Success
resolve(this.response);
} else {
// Something went wrong (404 etc.)
reject(new Error(this.statusText));
}
}
request.onerror = function () {
reject(new Error(
'XMLHttpRequest Error: '+this.statusText));
};
request.open('GET', url);
request.send();
});
}
Using it:
httpGet('http://example.com/file.txt').then(function (value) {
console.log('Contents: ' + value);
}, function (reason) {
console.error('Something went wrong', reason);
});
httpGet('story.json').then(function (response) {
return JSON.parse(response);
}).then(function (response) {
console.log("Parsed JSON:", response);
});
httpGet('/getBooksCollection').then(function (response) {
httpGet('/getBooksCollection/' + response[0].id);
}).then(function (bookData) {
console.log('First book: ' + bookData.name);
});
Using rejection callback
httpGet('/url').then(function (response) {
// Success
}, function (err) {// Rejection callback
console.error('Error: ', err);
});
Using catch
clausure:
httpGet('/url').then(function (response) {
// Handle response
}).catch(function () {
// Handle error
});
It's like:
httpGet('/url').then(function (response) {
// Handle response
}).then(undefined, function () {
// Handle error
});
Promise.all
"Gate"All the promises you pass in must fulfill for the returned promise to fulfill. If fulfilled, returns an array with all the promises values. If rejected, returns the first promise rejection reason.
Promise.race
"Latch"Only the first promise to resolve (fulfillment or rejection) "wins". It returns its fulfillment value or rejection reason.
var p1 = Promise.resolve( 42 );
var p2 = Promise.resolve( "Hello World" );
var p3 = Promise.reject( "Oops" );
Promise.race( [p1,p2,p3] )
.then( function(msg){
console.log( msg ); // 42
} );
Promise.all( [p1,p2,p3] )
.catch( function(err){
console.error( err ); // "Oops"
} );
Promise.all( [p1,p2] )
.then( function(msgs){
console.log( msgs ); // [42,"Hello World"]
} );
Also there are anti-patterns:
Generators are a special kind of function that can be
suspended and resumed (with the yield
operator). They are similar to other languages' coroutines, and allow to expressing async flow
control in a sequential, synchronous-looking fashion.