ECMAScript 6 Promises

  • Tutorial
There have already been articles on Habré about the wonderful Promises technology, which in the future will become part of the ECMAScript 6 standard, however, these articles did not contain a detailed description of why they are so useful and what are their advantages. In order to fill this gap, I decided to write this article.

Attention! This article is not an exhaustive guide. It is rather a PR of good and useful technology, a temptation that shows the positive aspects. The article is a compilation of several other articles, links below.

So what is Promise?


  • Promise is an object used as a stub for the result of some pending (and possibly asynchronous) calculation
  • A way to write serial / parallel asynchronous code as synchronous
  • Part of the ECMAScript 6 Standard


Important basics


  • The Promise constructor is an implementation of the Revealing Constructor Pattern
  • "Static" methods
  • Promise.resolve (val) and Promise.reject (val)
  • Promise.all and Promise.race
  • The concept of “Thenable” object
  • In a nutshell - this is an object that has .then () and .catch () methods


Status Promise


  1. Fulfilled - calculation was successful
  2. Rejected - error during calculation (any)
  3. Pending - the calculation has not yet completed (not fulfilled and not rejected)
  4. Settled - calculation completed (no matter how)


Examples


So, let's take a piece of synchronous code
function foo() {
	var a = ‘a’;
	a = a + ‘b’;
	a = a + ‘c’;
	return a;
}

And make it look like Promise on the outside:
function foo() {
	var a = ‘a’;
	a = a + ‘b’;
	a = a + ‘c’;
	return Promise.resolve(a);
}

The next step is to divide each step of the calculation:
function foo() {
	return Promise.resolve(‘a’)
		.then(function(a){ return a + ‘b’; })
		.then(function(a){ return a + ‘c’; });
}

Now each step can be made asynchronous, and all execution will continue to be sequential.
Let's go ahead, replace one of the steps with “as if an asynchronous function returning Promise”:
function getB(a){ return Promise.resolve(a + ‘b’); }
function foo() {
	return Promise.resolve(‘a’)
		.then(function(a){ return getB(a); })
		.then(function(a){ return a + ‘c’; });
}

Built-in functionality allows returning to then (cb ()) either an error (throw new Error ()) or a value (return a + 'c';) or the next Promise.

Parallelism


Imagine first you need to perform asynchronous action 1, then parallel to 2 and 3, and then 4.
asyncAction1()
	.then(function(res1){
		return Promise.all([async2(res1), async3(res1)]);
	})
	.then(function(arr){ // an array of values
		var res2 = arr[0], res3 = arr[1];
		return asyncAction4(res2, res3);
	})
	.then(…);


Error processing


The great thing about Promises is error handling. It doesn’t matter at what stage and at what depth of nesting an error occurred, whether it be reject or just an exception thrown, you can catch and process all this, or skip ahead.
asyncAction()
	.catch(function(rejection){
		// пытаемся понять, можно ли как-то обработать ошибку 
		if (rejection.code == ‘foo’) return ‘foo’;
		// никак нельзя, прокидываем ошибку дальше
		throw rejection;
	})
	.then(…) 
	.then(…)
	.catch(…);

Here we need to make the observation that if, we put
var p1 = new Promise(...),
    p2 = new Promise(...)
    p3 = Promise.all([p1, p2]);
p3.then(...).catch(...);

Catch will catch everything that went wrong (and no matter what and how) in p1, p2, p3 and any nested calls, which is wildly convenient. Reverse side - if catch () is not present, then the error will be quietly swallowed. However, libraries like Q, as a rule, have the ability to set a handler for uncaught errors where they can be displayed in the console or something else.

A word about anti-patterns


function anAsyncCall() {
	var promise = doSomethingAsync();
	promise.then(function(){
		somethingComplicated();
	});
	return promise;
}

And with a flick of the wrist we lost the second Promise. The fact is that each call to .then () or .catch () creates a new Promise, so if you created a new one and returned the old one, the new one will hang somewhere in the air and no one will know what the result of the calculation is. How to fight - just return the new Promise:
return promise.then(...);


Useful nishtyaki



Delayed execution


function delay(ms){
	return new Promise(function(resolve){
		setTimeout(resolve, ms);
	}
};

Usage example
delay(5000).then(…);


The simplest timeout


Since Promise can only be settled once (the rest is ignored), you can write something like
function timeout(promise, ms) {
	return new Promise(function (resolve, reject) {
		promise.then(resolve);
		setTimeout(function () {
			reject(new Error('Timeout’));
		}, ms);
	});
}
timeout(asyncAction(), 5000).then(…).catch(…);

Who first got up - that and slippers.

Slightly improved timeout


A slightly more obvious timeout example is through the “static” function Promise.race ().
Promise.race([
	asynchronousAction(),
	delay(5000).then(function () {
		throw new Error('Timed out');
	})
])
.then(function (text) { ... })
.catch(function (reason) { ... });


Important note on asynchrony


  1. The library controls the execution process, respectively, it manages how the result is delivered - synchronously or asynchronously
  2. At the same time, the Promises / A + specification requires that the last mode always be used - asynchronous
  3. Thus, we can always rely on the immediate execution of the promise.then (). Catch () code, etc., and not worry that some of their callbacks will eat all the processor time (with reservations, of course)


Why Promises are Better than Callbacks


  • They are part of the standard - smart people thought and developed everything for our convenience
  • “Practically” do not affect performance ( http://thanpol.as/javascript/promises-a-performance-hits-you-should-be-aware-of )
  • Try to resolve a non-trivial thread of execution on callbacks, preferably with error handling, if you do not write your implementation of Promises, the code will most likely be impossible to read and understand
  • All Promises / A + compatible libraries can accept each other's objects (Angular works great with Q / Native Promise / RSVP objects, etc.)
  • And Promise is better than Deferred, because the latter are two concepts in one, which is certainly bad, and besides this, the Revealing Constructor pattern almost guarantees that Promise was settled only within this constructor (well, or you’re malicious to yourself Pinocchio)


disadvantages


  1. Not suitable for recurring events (though Promises weren't written for that)
  2. Not suitable for streams (similar)
  3. The current implementation in browsers does not allow monitoring progress (they are also going to be included in the standard)


JQuery Note


The implementation of “Thennable” in jQuery is slightly different from the standard one - in the standard, the argument in callback is exactly one, in jQuery the number of arguments is greater, which does not interfere with using the object obtained from jQuery in the native implementation. As a rule, more than the first argument, nothing from jQuery is needed. Plus there is a design:
var jsPromise = Promise.resolve($.ajax('/whatever.json'));


Self read links



Also popular now: