Asynchrony. Asynchrony never changes. Node.js used asynchrony to get big rps for io operations. TC39 has added promises to the specification for dealing with Kolbak hell. Finally, we have standardized async / await. But asynchrony never changes. Wait, is it turning blue in the sky? It looks like bluebird carries in his beak a belt with tools for those of us who are tightly hooked on thenable objects and all this asynchronous noodles.
If someone is unfamiliar, bluebird is a library that implements the functionality of promis for javascript. If you are unlikely to drag it into the client build, as there is no 21Kb gzipped, then you just don’t have to use it on the server side. Bluebird is still faster than the native implementation. You may not believe the word, but download the repository and run benchmarks on the latest version of Node.js (9.xx). More details about the benefits can be found in a brief overview of the architectural principles of the library.
In addition to happiness, the library gives many methods straight from tomorrow, complementing the basic mechanism of promises. Therefore, I propose to get acquainted with the most interesting of the methods. I hope this will encourage you to dive deeper into the documentation, as there is still a lot of tasty stuff.
Let's start with a fairly easy and well-known, watching for new features in ECMAScript, namely - finally. The exact same method is now part of the specification (entered into the release ES2018). Allows you to register a handler that is triggered regardless of the final state of the promis (fullfiled, rejected).
// - 1 - // after fullfill -> always Promise.resolve(42) .then(() => console.log('after fullfill')) .catch(() => console.log('after reject')) .finally(() => console.log('always')); // - 2 - // after reject -> always Promise.reject(42) .then(() => console.log('after fullfill')) .catch(() => console.log('after reject')) .finally(() => console.log('always'));
This method, like the good old then and catch, returns a new promise to which you can subscribe. It is important that in the event of a transition to the rejected state, the handler in finally is not considered a successful error handling, so it will continue to propagate until the first catch handler.
// - 1 - // after fullfill -> always -> a bit later Promise.resolve(42) .then(() => console.log('after fullfill')) .finally(() => console.log('always')) .then(() => console.log('a bit later')); // - 2 - // after reject -> always -> a bit later Promise.reject(42) .catch(() => console.log('after reject')) .finally(() => console.log('always')) .then(() => console.log('a bit later')); // - 3 - // always -> after reject Promise.reject(42) .then(() => console.log('after fullfill')) .finally(() => console.log('always')) .then(() => console.log('never')) .catch(() => console.log('after reject'));
And, of course, you can return a promise from the finally handler. The remaining chain will wait for it to complete by calling subsequent handlers.
// always -> after 1s Promise.resolve(42) .finally(() => { console.log('always'); return delay(1000); }) .then(() => console.log('after 1s')); function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
Moving on. The guys pumped over the catch method - with it you can easily filter the errors that we want to handle:
class DeannonizationError extends Error {} class BigBrotherWatchingYouError extends Error {} // - 1 - // better run Promise.reject(new DeannonizationError()) .catch(DeannonizationError, () => console.log('better run')) .catch(BigBrotherWatchingYouError, () => console.log('too late')); // - 2 - // too late Promise.reject(new BigBrotherWatchingYouError()) .catch(DeannonizationError, () => console.log('better run')) .catch(BigBrotherWatchingYouError, () => console.log('too late')); // - 3 - // oh no Promise.reject(new BigBrotherWatchingYouError()) .catch(DeannonizationError, BigBrotherWatchingYouError, () => console.log('oh no'));
This encourages writing error handling in a more atomic style with good potential for code reuse. Also, in addition to the prototype, you can use the predicate function:
// predicate Promise.reject({ code: 42 }) .catch(error => error.code === 42, () => console.log('error 42')); // shorthand for checking properties Promise.reject({ code: 42 }) .catch({ code: 42 }, () => console.log('error 42'));
One of the most remarkable methods of the library and it is extremely strange that it is not in the standard - any.
// 42 Promise.any([ Promise.reject(40), // error Promise.reject(41), // error Promise.resolve(42), // success ]).then(x => console.log(x));
Allows you to wait for at least one promise from the transmitted array. If in more detail, the promise created by the any method will go to the fullfiled state when any of the promises go to this state. The handler in then gets the value from this allowed promise:
// 500 Promise.any([ delay(1000), delay(500), delay(700), ]).then(x => console.log(x)); function delay(ms) { return new Promise(resolve => setTimeout(() => resolve(ms), ms)); }
In case all transmitted promises are terminated with an error, the aggregating promise will also go into the rejected state. The handler in catch will receive a special error that combines the reasons for the failure of all promises. Note that the order of errors depends on the order of their occurrence, and not on the original order of promises.
// - 1 - // 40 -> 41 -> 42 Promise.any([ Promise.reject(40), Promise.reject(41), Promise.reject(42), ]).catch(error => error.forEach(x => console.log(x))); // - 2 - // 500 -> 700 -> 1000 Promise.any([ delayAndReject(1000), delayAndReject(500), delayAndReject(700), ]).catch(error => error.forEach(x => console.log(x))); function delayAndReject(ms) { return new Promise((resolve, reject) => setTimeout(() => reject(ms), ms)); }
In essence, the any method is a special version of the some method with the count parameter equal to 1. Thus, through some, we can explicitly set the conditions for the aggregate promise to go to the fulfilled state:
// [40, 41] Promise.some([ Promise.resolve(40), Promise.resolve(41), Promise.reject(42), ], 2).then(x => console.log(x));
If you often need to simultaneously run an asynchronous operation for each element of the array and then wait for all the results, then you are familiar with this code:
// [1, 2, 3] const promises = [1, 2, 3].map(x => Promise.resolve(x)); Promise.all(promises) .then(x => console.log(x));
Blue Bird provides us with a shortcut for this:
Promise.map([1, 2, 3], x => Promise.resolve(x)) .then(x => console.log(x));
The only thing you should pay attention to: for a function passed as a mapper, the third parameter instead of an array is its length. Also, the map method has a settings object passed after the mapper. At the moment there is only one option - concurrency - controlling how many promises can be run in parallel:
// start of 1000ms timer // start of 2000ms timer // end of 1000ms timer // start of 3000ms timer // end of 2000ms timer // end of 3000ms timer // after 4000ms Promise.map([1000, 2000, 3000], x => delay(x), { concurrency: 2 }) .then(x => console.log('after 4000ms')); function delay(ms) { console.log(`start of ${ms}ms timer`); return new Promise(resolve => setTimeout(() => { console.log(`end of ${ms}ms timer`); resolve(); }, ms)); }
And what will happen if you set the concurrency to 1? True, promises will be performed sequentially. There is also a shortcut for this:
// start of 1000ms timer // end of 1000ms timer // start of 2000ms timer // start of 3000ms timer // end of 2000ms timer // end of 3000ms timer // after 6000ms Promise.mapSeries([1000, 2000, 3000], x => delay(x)) .then(x => console.log('after 6000ms')); function delay(ms) { console.log(`start of ${ms}ms timer`); return new Promise(resolve => setTimeout(() => { console.log(`end of ${ms}ms timer`); resolve(); }, ms)); }
Often there is a situation when you need to transfer some intermediate data between promise handlers within the chain. You can use Promise.all and destructuring for this purpose. Another option would be to use a shared context bound to handlers in then and catch using the bind method:
// {x: 42, y: 43} Promise.resolve(42) .bind({}) .then(function (x) { this.x = x; return Promise.resolve(43); }) .then(function (y) { this.y = y; }) .then(function () { console.log(this) });
For cases where a function that returns a promise can have synchronous returns, you can use the method utility, which resolves them automatically. For example, it will be useful when memorizing asynchronous operations. Unlike try, method returns a higher order function:
Promise.method(semiAsyncFunction)() .then(x => console.log('I handle both sync and async results', x)); function semiAsyncFunction() { if (Math.random() > 0.5) { return 420; } return delay(42); } function delay(ms) { return new Promise(resolve => setTimeout(() => resolve(ms), ms)); }
The tap method is useful if you need to insert side effects that do not change the data into an existing chain, for example, for logging:
// log 42 // process 42 Promise.resolve(42) .tap(x => console.log(`log ${x}`)) .then(x => console.log(`process ${x}`));
If the side effect is an asynchronous operation, and it is important to wait for its execution, we usually return the promise from the handler:
// start logging // log 42 // process 42 Promise.resolve(42) .tap(x => asyncLogging(x)) .then(x => console.log(`process ${x}`)); function asyncLogging(x) { console.log('start logging'); return new Promise(resolve => setTimeout(() => { console.log(`log ${x}`); resolve(); }, 1000)); }
There is also a version of the method for errors:
// log error 42 // process error 42 Promise.reject(42) .tapCatch(x => console.log(`log error ${x}`)) .catch(x => console.log(`process error ${x}`));
Also, as with catch, you can filter:
class DeannonizationError extends Error {} class BigBrotherWatchingYouError extends Error {} // log deannonimization // process deannonimization Promise.reject(new DeannonizationError()) .tapCatch(DeannonizationError, x => console.log('log deannonimization')) .tapCatch(BigBrotherWatchingYouError, x => console.log('log bbwy')) .catch(DeannonizationError, () => console.log('process deannonimization')) .catch(BigBrotherWatchingYouError, () => console.log('process bbwy')); // log bbwy // process bbwy Promise.reject(new BigBrotherWatchingYouError()) .tapCatch(DeannonizationError, x => console.log('log deannonimization')) .tapCatch(BigBrotherWatchingYouError, x => console.log('log bbwy')) .catch(DeannonizationError, () => console.log('process deannonimization')) .catch(BigBrotherWatchingYouError, () => console.log('process bbwy'));
The next feature is being reviewed by TC39 as part of a broader topic - cancellation of asynchronous operations. While it has not yet been delivered, we can be content with little and learn how to cancel promises:
Promise.config({ cancellation: true }); const promise = delay(1000) .then(() => console.log('We will never see this')); promise.cancel(); function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
Some asynchronous operations can be undone. When creating a promise, Bluebird will provide you with a special method for registering a callback called upon cancellation:
Promise.config({ cancellation: true }); const promise = delay(1000) .then(() => console.log('We will never see this')); promise.cancel(); function delay(ms) { return new Promise((resolve, reject, onCancel) => { const timer = setTimeout(() => { console.log('and this one too'); resolve(); }, ms); onCancel(() => clearTimeout(timer)); }); }
It is useful to set time limits for the operation. Then we have a timeout method that will reject the promise with a TimeoutError error in the event of a timeout:
// Time's up! delay(1000) .timeout(100) .then(() => console.log(`We will never see this`)) .catch(Promise.TimeoutError, error => console.log(`Time's up!`)) function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
And finally, for mental relief. If, due to insuperable circumstances, it is necessary to postpone the launch of an asynchronous operation, then the delay method will help:
Promise.delay(1000) .then(() => console.log(`after 1s`));
On this we should say goodbye. Try the blue bird in your pet projects, and then take it with you to production. See you at JS open spaces!
Source: https://habr.com/ru/post/352064/
All Articles