Bluebird:非同期ツールベルト

非同期。 非同期性は決して変わりません。 Node.jsは非同期を使用して、io操作の優れたrpを取得しました。 TC39は、コールバック地獄の仕様に約束を追加しました。 最後に、async / awaitを標準化しました。 しかし、非同期性は決して変わりません。 空で青くなりますか? ブルーバードはくちばしに道具ベルトを付けているように見えます。私たちにとっては、熱心なオブジェクトとこのすべての非同期麺にしっかりと引っかかっています。













誰もが慣れていない場合、bluebirdはjavascriptのpromiseの機能を実装するライブラリです。 21Kbをgzip圧縮したように、クライアントアセンブリにドラッグする可能性が低い場合は、サーバー側で使用しないという道徳的権利がありません。 Bluebirdは、ネイティブ実装よりも高速です。 言葉は通用しませんが、リポジトリをダウンロードし、Node.jsの最新バージョン(9.xx)でベンチマークを実行してください。 ライブラリのアーキテクチャの原則の簡単な概要で、利点について詳しく読むことができます。







幸福に加えて、ライブラリは明日から多くの方法を提示し、約束の基本的なメカニズムを補完します。 したがって、私は最も興味深い方法に精通することを提案します。 まだまだ食べられることがたくさんあるので、これがあなたがドキュメントをより深く掘り下げることを奨励することを願っています。







かなり軽くて有名なものから始めましょう。ECMAScriptの新機能、つまり最後に注目しましょう。 まったく同じメソッドが仕様の一部になりました(ES2018リリースに含まれています)。 promiseの最終状態(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'));
      
      





このメソッドは、古き良きthenやcatchと同様に、サブスクライブできる新しいpromiseを返します。 拒否状態への遷移の場合、finallyハンドラーはエラー処理の成功とは見なされないため、最初のcatchハンドラーに伝搬し続けることが重要です。







 // - 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'));
      
      





そして、もちろん、finallyハンドラーからpromiseを返すことができます。 残りのチェーンは、後続のハンドラーを呼び出して完了を待機します。







 // 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)); }
      
      





先に進みます。 みんながcatchメソッドをポンプアップしました-これを使えば、処理したいエラーを簡単にフィルタリングできます:







 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'));
      
      





これにより、コードを再利用できる可能性のある、よりアトミックなスタイルでエラー処理を記述することができます。 また、プロトタイプに加えて、述語関数を使用できます。







 // 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'));
      
      





ライブラリの最も注目すべきメソッドの1つであり、標準にないことは非常に奇妙です。







 // 42 Promise.any([ Promise.reject(40), // error Promise.reject(41), // error Promise.resolve(42), // success ]).then(x => console.log(x));
      
      





転送された配列から少なくとも1つのプロミスが完了するまで待つことができます。 より詳細には、anyメソッドによって作成されたpromiseは、promiseのいずれかがこの状態になるとfullfiled状態になります。 ハンドラーは、この許可されたプロミスから値を取得します。







 // 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)); }
      
      





送信されたすべてのプロミスが失敗すると、アグリゲートプロミスも拒否状態になります。 catchハンドラーは、すべてのプロミスの拒否の理由を組み合わせた特別なエラーを受け取ります 。 エラーの順序は、約束の最初の順序ではなく、発生の順序に依存することに注意してください。







 // - 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)); }
      
      





実際、anyメソッドは、countパラメーターが1に等しいsomeメソッドの特別なバージョンです。したがって、一部のメソッドを使用して、集約プロミスが満たされた状態に移行するための条件を明示的に設定できます。







 // [40, 41] Promise.some([ Promise.resolve(40), Promise.resolve(41), Promise.reject(42), ], 2).then(x => console.log(x));
      
      





配列の各要素に対して非同期操作を並行して実行し、すべての結果を待つ必要があることが多い場合、このコードはおなじみです。







 // [1, 2, 3] const promises = [1, 2, 3].map(x => Promise.resolve(x)); Promise.all(promises) .then(x => console.log(x));
      
      





Bluebirdは、このためのショートカットを提供します。







 Promise.map([1, 2, 3], x => Promise.resolve(x)) .then(x => console.log(x));
      
      





注意する必要があるのは、マッパーとして渡される関数の場合、配列の代わりに3番目のパラメーターがその長さだけであることに注意してください。 また、mapメソッドには、マッパーの後に渡される設定のオブジェクトがあります。 現時点では、並行性-並行して起動できるプロミスの数を制御するオプションは1つだけです。







 // 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)); }
      
      





しかし、並行性を1に設定するとどうなりますか? 確かに、約束は順番に実行されます。 このために、ショートカットもあります。







 // 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)); }
      
      





チェーン内のプロミスハンドラー間で中間データを転送する必要がある場合がよくあります。 これらの目的のためにPromise.allとデストラクタリングを使用できます。 別のオプションは、thenでハンドラーにバインドされた共通コンテキストを使用し、bindメソッドを使用してキャッチすることです。







 // {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) });
      
      





promiseを返す関数に同期戻りがある場合、メソッドユーティリティを使用して自動モードで解決できます。 たとえば、非同期操作をメモするのに役立ちます。 tryとは異なり、メソッドは高次関数を返します。







 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)); }
      
      





tapメソッドは、たとえばロギングのために、データを変更しない副作用を既存のチェーンに挿入する必要がある場合に便利です。







 // log 42 // process 42 Promise.resolve(42) .tap(x => console.log(`log ${x}`)) .then(x => console.log(`process ${x}`));
      
      





副作用が非同期操作であり、その実行を待つことが重要である場合、ハンドラーからプロミスを習慣的に返​​します。







 // 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)); }
      
      





エラー用のメソッドのバージョンもあります。







 // log error 42 // process error 42 Promise.reject(42) .tapCatch(x => console.log(`log error ${x}`)) .catch(x => console.log(`process error ${x}`));
      
      





また、catchと同様に、フィルタリングを実行できます。







 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'));
      
      





次の機能は、より広範なトピックの一部としてTC39で検討中です-非同期操作のキャンセル。 まだ配信されていませんが、小さなことで満足し、約束をキャンセルする方法を学ぶことができます。







 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)); }
      
      





一部の非同期操作は元に戻すことができます。 約束を作成する際、Bluebirdはキャンセル時に呼び出されるコールバックを登録するための特別な方法を提供します:







 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)); }); }
      
      





操作の時間制限を設定すると便利です。 次に、タイムアウトメソッドを自由に使用できます。このメソッドは、時間切れの場合にTimeoutErrorエラーでプロミスを拒否します。







 // 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)); }
      
      





そして最後に、精神的な荷降ろしのために。 乗り越えられない状況のために、非同期操作の開始を延期する必要がある場合は、遅延メソッドが役立ちます。







 Promise.delay(1000) .then(() => console.log(`after 1s`));
      
      





これで私たちは別れを告げるべきです。 ペットプロジェクトでbluebirdを試してから、本番環境に持って行きます。 JSオープンスペースでお会いしましょう!








All Articles