序文非同期プログラミングでは、Promise が重要な役割を果たし、従来のソリューション (コールバック関数やイベント) よりも合理的かつ強力です。皆さんの中には、2020年なのになぜまだPromiseについて話しているのか、という疑問を持つ人もいるかもしれません。実際、友人の中には、ほぼ毎日接しているこの「古い友人」を理解している人もいるようですが、深く掘り下げてみると、多くの疑問が湧いてくるかもしれません。この記事は、この馴染みのある見知らぬ人、Promise について、より深く理解するのに役立ちます。 基本的な使い方文法new Promise( function(resolve, deny) {...} /* 実行者 */ )
非同期プログラミングを管理するために Promise が使用されていることに注意してください。Promise 自体は非同期ではありません。新しい Promise が作成されると、executor 関数がすぐに実行されます。ただし、通常は executor 関数内で非同期操作を処理します。たとえば、次のコードでは、先頭に 2 が出力されます。 p1 = new Promise(()=>{とする タイムアウトを設定します(()=>{ コンソール.log(1) },1000) コンソール.log(2) }) コンソール.log(3) // 2 3 1 Promise はコールバック関数の遅延バインディング技術を使用します。resolve 関数が実行されると、コールバック関数はまだバインドされていないため、コールバック関数の実行は延期することしかできません。これは具体的にどういう意味ですか?次の例を見てみましょう。 p1 = new Promise((resolve,reject)=>{とする コンソールログ(1); 解決('波の中のボート') コンソール.log(2) }) // then: 後処理の成功または失敗のメソッドを設定します p1.then(result=>{ //p1 遅延バインディングコールバック関数 console.log('success'+result) },理由=>{ console.log('失敗' + 理由) }) コンソール.log(3) // 1 // 2 // 3 // 波の中の成功 新しい Promise が作成されると、まず executor 関数が実行され、1 と 2 が出力されます。Promise が解決されると、マイクロタスクがトリガーされるか、同期タスクが続行されます。 p1.then が実行されると、2 つの関数が格納され (この時点では、この 2 つの関数は実行されていません)、3 が出力されます。この時点で、同期タスクが完了し、最後にマイクロタスクが実行され、.then 内の成功したメソッドが実行されます。 エラー処理Promise オブジェクトのエラーは「バブリング」の性質を持ち、onReject 関数によって処理されるか、catch ステートメントによってキャプチャされるまで返されます。この「バブリング」機能を使用すると、各 Promise オブジェクトで例外を個別にキャッチする必要がなくなります。 thenに遭遇すると、成功または失敗メソッドを実行しますが、このメソッドが現在のthenで定義されていない場合は、次の対応する関数に延期されます。 関数実行者(解決、拒否){ rand = Math.random() とします。 コンソール.log(1) console.log(ランダム) (ランダム > 0.5)の場合{ 解決する() } それ以外 { 拒否する() } } var p0 = 新しい Promise(エグゼキュータ) var p1 = p0.then((値) => { コンソールログ('成功-1') 新しい Promise(executor) を返します }) var p2 = p1.then((値) => { コンソールログ('成功-2') 新しい Promise(executor) を返します }) p2.catch((エラー) => { console.log('エラー', エラー) }) コンソール.log(2) このコードには、p0 ~ p2 の 3 つの Promise オブジェクトがあります。どのオブジェクトが例外をスローしたとしても、最後のオブジェクト p2.catch を使用して例外をキャッチできます。このようにして、すべての Promise オブジェクトのエラーを 1 つの関数にマージして処理できるため、各タスクが例外を個別に処理する必要があるという問題が解決されます。 この方法では、ネストされた呼び出しと頻繁なエラー処理が排除され、記述するコードはよりエレガントになり、人間の線形思考に沿ったものになります。 プロミスチェーン呼び出し複数の Promise を連結して一連の異なるステップを表すことができることは誰もが知っています。このアプローチの鍵となるのは、Promise の次の 2 つの固有の動作です。
まず、次の例を通してこの文の意味を説明し、次にチェーン呼び出しの実行プロセスを詳しく紹介します。 p1 = new Promise((resolve, reject) => {とする resolve(100) // 次に実行される成功したメソッドを決定します}) // p1に接続 p2=p1とします。すると(結果=>{ console.log('成功 1 '+結果) Promise.reject(1) を返す // 新しい Promise インスタンスを返します。これは現在のインスタンスが失敗であると判定し、次の then メソッドが実行されます}, reason=>{ console.log('失敗 1 '+理由) 200を返す }) // p2に接続 p3=p2とします。すると(結果=>{ console.log('成功 2' + 結果) },理由=>{ console.log('失敗 2 '+理由) }) // 成功 1 100 // 失敗 2 1 最初の呼び出しで作成され返されたPromise p2を、Promise.reject(1)を返すことで完了します。 p2のthen呼び出しは、実行されると、return Promise.reject(1)ステートメントから実行値を受け取ります。もちろん、p2.then は別の新しいプロミスを作成し、それを変数 p3 に格納できます。 new Promise によって生成されたインスタンスが成功するか失敗するかは、executor 関数の実行時に、resolve または reject が実行されるか、executor 関数の実行中に異常なエラーが発生するかによって異なります。どちらの場合も、インスタンスのステータスは失敗に変更されます。 then の実行時に p2 によって返される新しいインスタンスの状態によって、次の then でどのメソッドが実行されるかが決まります。状況はいくつかあります。
別の例を見てみましょう 新しいPromise(resolve=>{ resolve(a) // エラー // このエグゼキュータ関数の実行には例外エラーがあり、次の失敗メソッドが実行されることになります}).then(result=>{ console.log(`成功: ${result}`) 結果*10を返す },理由=>{ console.log(`失敗: ${reason}`) // この文を実行すると、例外は発生せず、失敗した Promise インスタンスが返されるので、次の then success メソッドが実行されます // ここでは return はなく、最後に undefined が返されます })。そして、結果=>{ console.log(`成功: ${result}`) },理由=>{ console.log(`失敗: ${reason}`) }) // 失敗: ReferenceError: a が定義されていません // 成功: 未定義 非同期と待機上記の例から、Promise を使用するとコールバック地獄の問題をうまく解決できるものの、このメソッドは Promise の then() メソッドでいっぱいであることがわかります。処理フローがさらに複雑な場合、コード全体が then メソッドでいっぱいになり、セマンティクスが明確でなくなり、コードが実行フローをうまく表現できなくなります。 ES7 で追加された新しい非同期プログラミング方法である async/await の実装は、Promise に基づいています。簡単に言うと、async 関数はジェネレーターの構文糖である Promise オブジェクトを返します。多くの人は、async/await が非同期操作の究極のソリューションであると考えています。
ただし、await は非同期コードを同期コードに変換するため、いくつかの欠点もあります。複数の非同期コードに依存関係がないのに await を使用すると、パフォーマンスが低下します。 非同期関数テスト(){ // 次のコードに依存関係がない場合は、Promise.all を使用できます // 依存関係がある場合は、実際にはコールバック地獄を解決する例です await fetch(url1) フェッチを待つ(url2) フェッチを待つ(url3) } 次のコードを見ると、何が印刷されるかわかりますか? p1 = Promise.resolve(1) とします。 p2 = new Promise(resolve => { とする タイムアウトを設定する(() => { 解決する(2) }, 1000) }) 非同期関数fn() { コンソール.log(1) // コードがこの行まで実行されると(この行を最初に配置)、非同期マイクロタスクを構築します // プロミスが結果を返すのを待ちます。また、以下のコード await もタスク キューにリストされます let result1 = await p2 コンソール.log(3) 結果2 = p1を待機します コンソール.log(4) } 関数() コンソール.log(2) // 1 2 3 4 await の右側に表現されたロジックが promise である場合、await は promise の戻り結果を待ちます。戻り値の状態が解決された場合にのみ結果が返されます。promise が失敗状態の場合、await は戻り結果を受け取らず、await の下のコードは実行を継続しません。 p1 = Promise.reject(100) とします。 非同期関数fn1() { 結果 = p1 を待機します console.log(1) //このコード行は実行されません} もっと複雑な質問を見てみましょう。 コンソール.log(1) タイムアウトを設定します(()=>{console.log(2)},1000) 非同期関数 fn(){ コンソール.log(3) タイムアウトを設定します(()=>{console.log(4)},20) Promise.reject() を返す } 非同期関数run() { コンソール.log(5) fn() を待つ コンソール.log(6) } 走る() //実行には約150ミリ秒かかります for(let i=0;i<90000000;i++){} タイムアウトを設定します(()=>{ コンソール.log(7) 新しいPromise(resolve=>{ コンソール.log(8) 解決する() }).then(()=>{console.log(9)}) },0) コンソール.log(10) // 1 5 3 10 4 7 8 9 2 この質問をする前に、読者は次のことを理解する必要があります。
次に、段階的に分析します。
よく使われる方法1. Promise.resolve() Promise.resolve(value) メソッドは、指定された値で解決される Promise オブジェクトを返します。 Promise.resolve('foo') // new Promise(resolve =>resolved('foo')) と同等 Promise.resolve メソッドのパラメータは 4 つのケースに分かれています。 (1)パラメータはPromiseインスタンスである パラメータが Promise インスタンスの場合、Promise.resolve は変更を加えずにインスタンスをそのまま返します。 const p1 = new Promise(function (resolve, reject) { setTimeout(() => 拒否(新しいエラー('fail'))、3000) }) const p2 = new Promise(function (resolve, reject) { タイムアウトを設定します(() => 解決します(p1), 1000) }) 2ページ目 .then(結果 => console.log(結果)) .catch(エラー => console.log(エラー)) // エラー: 失敗 上記のコードでは、p1 は Promise であり、3 秒後に拒否されます。 1 秒後に p2 の状態が変化し、resolve メソッドは p1 を返します。 p2 は別の Promise を返すため、p2 の状態自体は無効になり、p2 の状態は p1 の状態によって決定されます。したがって、後続のthenステートメントはすべて後者(p1)を対象とします。さらに 2 秒後、p1 は拒否され、catch メソッドで指定されたコールバック関数がトリガーされます。 (2)パラメータはthenメソッドを持つオブジェクトではない、またはオブジェクトではない Promise.resolve("成功").then(function(値) { // Promise.resolve メソッドのパラメータは同時にコールバック関数に渡されます。 console.log(値); // "成功" }, 関数(値) { // は呼び出されません }); (3)パラメータなし Promise.resolve() メソッドを使用すると、パラメータなしで呼び出して、解決された状態の Promise オブジェクトを直接返すことができます。 Promise オブジェクトを取得する場合、より便利な方法は Promise.resolve() メソッドを直接呼び出すことです。 Promise.resolve().then(関数() { コンソールログ('2'); }); コンソールにログ出力します。 // 1 2 (4)パラメータはthenableオブジェクトである thenable オブジェクトは、then メソッドを持つオブジェクトを参照します。Promise.resolve メソッドは、このオブジェクトを Promise オブジェクトに変換し、その後すぐに thenable オブジェクトの then メソッドを実行します。 thenable = { 次に: 関数(解決、拒否) { 解決する(42); } }; p1 = Promise.resolve(thenable); とします。 p1.then(関数(値) { console.log(値); // 42 }); 2. Promise.reject()Promise.reject() メソッドは、拒否理由を含む Promise オブジェクトを返します。 新しいPromise((resolve,reject) => { 拒否(新しいエラー("error")); }); // Promise.reject(new Error("Error")); と同等 // 使用方法 Promise.reject(new Error("BOOM!")).catch(error => { コンソールエラー(エラー); }); 注意すべき点は、resolve または reject を呼び出した後、Promise のミッションは完了し、後続の操作は then メソッド内に配置する必要があり、resolve または reject の直後に記述してはならないことです。したがって、驚くことがないように、 return ステートメントをそれらの前に置くことをお勧めします。 新しい Promise((resolve, 拒否) => { 拒否(1)を返す // 次のステートメントは実行されません console.log(2); }) 3. Promise.all()p1 = Promise.resolve(1) とします。 p2 = new Promise(resolve => { とする タイムアウトを設定する(() => { 解決する(2) }, 1000) }) p3 = Promise.resolve(3) とします。 プロミス.all([p3, p2, p1]) .then(結果 => { // 結果はインスタンスが配列に書き込まれた順序で返されます console.log(result) // [ 3, 2, 1 ] }) .catch(理由 => { console.log("失敗:理由") }) Promise.all は新しい Promise オブジェクトを生成して返すので、Promise インスタンスのすべてのメソッドを使用できます。このメソッドは、パラメータとして渡された Promise 配列内のすべての Promise オブジェクトが解決されたときに返され、新しく作成された Promise はこれらの Promise の値を使用します。 パラメータ内のいずれかの Promise が拒否された場合、Promise.all 呼び出し全体が直ちに終了し、拒否の新しい Promise オブジェクトが返されます。 4. Promise.allSettled()場合によっては、非同期操作の結果ではなく、これらの操作が完了したかどうかだけが重要になることがあります。このとき、ES2020 で導入された Promise.allSettled() メソッドが非常に役立ちます。この方法がなければ、すべての操作が完了したことを確認するのは面倒です。これは Promise.all() メソッドでは不可能です。 次のようなシナリオがあるとします。ページには 3 つの領域があり、それぞれが独立した 3 つのインターフェース データに対応しています。Promise.all を使用して 3 つのインターフェースを同時に要求します。いずれかのインターフェースに例外が発生すると、ステータスは拒否され、ページ内の 3 つの領域のデータが出力されなくなります。明らかに、この状況は受け入れられません。Promise.allSettled の登場により、この問題を解決できます。 Promise.allSettled([ Promise.reject({ code: 500, msg: 'サービス例外' }), Promise.resolve({ コード: 200, リスト: [] }), Promise.resolve({ コード: 200, リスト: [] }) ]).then(res => { コンソール.log(res) /* 0: {ステータス: "拒否"、理由: {…}} 1: {ステータス: "完了"、値: {…}} 2: {ステータス: "fulfilled", 値: {…}} */ // 拒否ステータスを除外し、ページ領域データを可能な限り多く確保する RenderContent( res.filter(el => { el.status !== '拒否' を返します }) ) }) Promise.allSettled は Promise.all に似ています。Promise の配列をパラメータとして受け取り、新しい Promise を返します。唯一の違いは、短絡処理を行わないことです。つまり、すべての Promise が処理されると、正常に処理されたかどうかに関係なく、各 Promise のステータスを取得できます。 5. Promise.race()Promise.all() メソッドの効果は「より遅く走る方がコールバックを実行する」というものなので、「より速く走る方がコールバックを実行する」ことに対応する別のメソッドが Promise.race() メソッドであり、この単語はもともとレースを意味します。 race の使用方法は all と同じで、promise オブジェクトの配列をパラメーターとして受け取ります。 Promise.all は、受信したすべてのオブジェクト プロミスが FulFilled または Rejected になるまで、後続の処理を続行しません。一方、Promise.race は、1 つのプロミス オブジェクトが FulFilled または Rejected 状態になる限り、後続の処理を続行します。 // `delay` ミリ秒後に解決を実行します 関数timerPromisify(delay) { 新しいPromiseを返します(resolve => { タイムアウトを設定する(() => { 解決(遅延) }、 遅れ); }); } // いずれかのプロミスが解決または拒否された場合、プログラムは実行を停止します。Promise.race([ タイマーPromisefy(1)、 タイマー約束(32) タイマー約束(64) ]).then(関数 (値) { console.log(値); // => 1 }); 上記のコードは、それぞれ 1 ミリ秒、32 ミリ秒、64 ミリ秒後に確認され、FulFilled になる 3 つの promise オブジェクトを作成し、最初のオブジェクトが確認されてから 1 ミリ秒後に .then によって登録されたコールバック関数が呼び出されます。 6. Promise.prototype.finally()ES9 では、Promise を返す finally() メソッドが追加されました。プロミスの終了時に、それが満たされたか拒否されたかに関係なく、指定されたコールバック関数が実行されます。これにより、Promise が正常に満たされたかどうかに関係なく、実行する必要があるコードを渡す方法が提供されます。これにより、then() と catch() の両方で同じステートメントを 1 回ずつ記述する必要がある状況を回避できます。 たとえば、リクエストを送信する前には読み込み画面が表示されます。リクエストが送信された後は、リクエストにエラーがあるかどうかに関係なく、読み込み画面をオフにしたいと考えています。 this.loading = true リクエスト() .then((res) => { // 何かをする }) .catch(() => { // エラーをログに記録 }) .finally(() => { this.loading = false }) finally メソッドのコールバック関数はパラメーターを受け入れません。これは、finally メソッド内の操作が状態から独立し、Promise の実行結果に依存しないことを示します。 実用化赤色のライトが 3 秒ごとに 1 回点灯し、緑色のライトが 1 秒ごとに 1 回点灯し、黄色のライトが 2 秒ごとに 1 回点灯するという要件があるとします。3 つのライトを交互に繰り返し点灯させるにはどうすればよいでしょうか。 関数 red() { コンソールログ('赤'); } 関数グリーン() { console.log('緑'); } 関数イエロー() { console.log('黄色'); } この問題の複雑さは、一度点灯したら終了する 1 回限りの処理ではなく、「交互に繰り返し」点灯する必要があることにあります。これは再帰によって実現できます。 // プロミスで実装する let task = (timer, light) => { 新しい Promise を返します ((resolve, reject) => { タイムアウトを設定する(() => { if (ライト === '赤') { 赤() } if (ライト === '緑') { 緑() } if (light === '黄色') { 黄色() } 解決する() }, タイマー); }) } ステップ = () => { タスク(3000, '赤') .then(() => task(1000, 'green')) .then(() => task(2000, '黄色')) .then(ステップ) } ステップ() 同じことは async/await でも実現できます。 // async/await 実装 let step = async () => { タスクを待機します(3000, '赤') タスクを待機します(1000, '緑') タスクを待機します(2000, '黄色') ステップ() } ステップ() async/await を使用すると、同期コードのスタイルで非同期コードを記述できます。async/await ソリューションの方が直感的であることは間違いありませんが、Promise を深く理解することが async/await を習得するための基礎となります。 上記は、Javascript 非同期プログラミングの詳細です。Javascript 非同期プログラミングの詳細については、123WORDPRESS.COM の他の関連記事に注目してください。 以下もご興味があるかもしれません:
|
背景CVE-2021-21972 VMware vCenter における認証されていないコマンド実行...
目次序文解決具体的な実装満たすべき前提条件質問序文テーブルをよく使用します。データ量が多い場合は直接...
1. 概要MySQL バージョン: 5.6.21ダウンロードアドレス: https://dev.my...
実際のプロジェクト開発では、多数のクエリや挿入、特にマルチスレッド挿入など、データベースに大きな負荷...
1. MySQL でグローバル変数を変更するには 2 つの方法があります。方法 1: my.ini ...
この記事では、カードフリップ効果を実現するためのVueの具体的なコードを例として紹介します。具体的な...
目次序文なぜユニットテストを導入するのですか?ユニットテストの概要テスト開発パターン1. テスト駆動...
react-native インストールプロセス1.npx react-native init Awe...
Debian の紹介Debian は、広い意味では、フリーなオペレーティング システムの作成に専念...
MySQLインストールチュートリアル、参考までに具体的な内容は次のとおりです。 1. ダウンロードY...
はじめに: 時間ポイントによる MySQL データベースの復旧どの企業にとっても、データは最も価値の...
開発に携わっている友人、特に MySQL に関係のある友人は、非常に遅い MySQL クエリに遭遇す...
ダウンロードしてインストールします。まず、システムに MySQL または MariaDB があるかど...
この記事では、セキュリティ、使用方法、同時処理などを通じて、MySQL トランザクションとデータの一...
序文「大規模なフロントエンド プロジェクト向け」に設計されたフロントエンド フレームワークである A...