ホームページ > 記事 > ウェブフロントエンド > ES6 非同期 + 待機同期/非同期ソリューション
この記事では、ES6 の async+await 同期/非同期ソリューションの詳細な説明を主に紹介します。この記事では、async + await のブロックを解除するための最も簡潔な方法を使用します。興味のある方は詳細をご覧ください。
非同期プログラミングは常に JavaScript の主要な問題です。プログラミング。非同期ソリューションに関しては、ES6 でまず状態管理に基づく Promise が登場し、次に Generator 関数 + co 関数、そして ES7 の async + await ソリューションが登場しました。
この記事は、最も簡潔な方法で非同期 + 待機のブロックを解除することを目的としています。
非同期プログラミングのいくつかのシナリオ
よくある質問から始めましょう: for ループで反復シーケンスを非同期に出力するにはどうすればよいですか?
この質問に答えるために、クロージャまたは ES6 で指定されている let ブロックレベルのスコープを使用することは簡単に考えられます。
for (let val of [1, 2, 3, 4]) { setTimeout(() => console.log(val),100); } // => 预期结果依次为:1, 2, 3, 4
ここで説明しているのは、均等に発生する非同期イベントであり、あらかじめ決められた順番で非同期キューにキューイングされて実行を待っています。
非同期が均等に発生しない場合、非同期キューに登録される順番が狂います。
for (let val of [1, 2, 3, 4]) { setTimeout(() => console.log(val), 100 * Math.random()); } // => 实际结果是随机的,依次为:4, 2, 3, 1
返される結果は順序が狂っていて制御不能ですが、これが最も現実的な非同期です。しかし、別の状況として、ループ内で前の非同期実行を完了させ、次の非同期実行を再度実行したい場合はどうすればよいでしょうか?
for (let val of ['a', 'b', 'c', 'd']) { // a 执行完后,进入下一个循环 // 执行 b,依此类推 }
これは単なる複数の非同期「シリアル」ではありませんか!
非同期操作をコールバックに入れ子にしてコールバックする方法で、この問題は解決されます。あるいは、Promise + then() を使用してレイヤーをネストすることでも、問題を解決できる可能性があります。ただし、このネストメソッドをループで記述することに固執すると、非常に手間がかかると思います。もっと良い方法はないでしょうか?
非同期同期ソリューション
想像してみてください。データのバッチをサーバーに送信したい場合、前のバッチが正常に送信された場合 (つまり、サーバーが成功の応答を返した場合) にのみ、次のデータ バッチを送信できるようになります。送信されない場合、送信は終了します。これは、「for ループ内の相互依存した非同期操作」の典型的な例です。
明らかに、この「シリアル」非同期は実際には同期とみなすことができます。アウトオブオーダーの非同期よりも時間がかかります。論理的に言えば、ブロックを「スキップ」して時間を短縮するために、プログラムを非同期で実行する必要があります。しかし逆に、一連の非同期「シリアル」が必要な場合、どのように適切にプログラムすればよいでしょうか?
この「シリアル」非同期については、ES6 はこの問題を非常に簡単に解決します。
async function task () { for (let val of [1, 2, 3, 4]) { // await 是要等待响应的 let result = await send(val); if (!result) { break; } } } task();
文字通り、このサイクルです。結果が得られたら、次のサイクルが実行されます。したがって、ループが終了するまで、実行するたびにループが一時停止 (「スタック」) されます。このコーディング実装により、ネストされた「コールバック地獄」の問題が効果的に排除され、認知的困難が軽減されます。
これは、非同期の問題を同期するための解決策です。この解決策に関して、Promise が主に非同期コールバックの問題を解決するのであれば、async + await は主に非同期の問題の同期化と非同期プログラミングの認知的負担の軽減の問題を解決します。
async + await 「外側は違うけど内側は同じ」
以前この API に触れたとき、面倒なドキュメントを見て、async + await は主に非同期の問題を解決するために使用されるものだと思いましたそしてそれらを同期させます。
実際にはそうではありません。上記の例からわかるように、async キーワードは非同期関数を宣言します。この非同期関数の本体には、動作が同期的に実行されることを通知する await ステートメントの行があり、上下の隣接するコードが実行されます。一行ずつ順番に。
この形式的なものをもう一度翻訳すると、次のようになります。
1. async 関数が実行された後、常に Promise オブジェクトが返されます
2. await が配置されているステートメントの行は同期です
このうち、1 は From を説明します。外部では、タスクメソッドは実行後に Promise オブジェクトを返すので、タスクが非同期メソッドであることがわかります。次のように使用されることに疑いの余地はありません。
task().then((val) => {alert(val)}) .then((val) => {alert(val)})
2 は、タスク関数内で非同期が同期に「カット」されたことを示しています。全体としては、実行に少し時間がかかる関数にすぎません。
1と2を組み合わせると、形式的には「タスクは全体としては非同期関数であり、内部はすべて同期している」ことを「外見は違うが中身は同じ」といいます。
全体は非同期関数であり、理解するのは難しくありません。実装に関して言えば、言語レベルでは、async キーワードが呼び出されるとき、関数の実行の最後に Promise が強制的に追加されます。その応答は次のとおりです。内部は同期しています。実際、await 呼び出しにより、次のステートメント (関数) が再帰的に実行されます。結果が取得され、そのステータスが変更されるまでは解決されません。解決が完了した後でのみ、コードの await 行が完了したとみなされます。次の行まで実行されます。したがって、外側に大きな for ループがありますが、for ループ全体が順番にシリアル化されます。
したがって、上記のフレームワークの外観だけから、async + await の意味を理解するのは難しくありません。使い方はとても簡単ですが、Promise はマスターする必要がある基本的な部分です。
秉承本次《重读 ES6》系列的原则,不过多追求理解细节和具体实现过程。我们继续巩固一下这个 “形式化” 的理解。
async + await 的进一步理解
有这样的一个异步操作 longTimeTask,已经用 Promise 进行了包装。借助该函数进行一系列验证。
const longTimeTask = function (time) { return new Promise((resolve, reject) => { setTimeout(()=>{ console.log(`等了 ${time||'xx'} 年,终于回信了`); resolve({'msg': 'task done'}); }, time||1000) }) }
async 函数的执行情况
如果,想查看 async exec1 函数的返回结果,以及 await 命令的执行结果:
const exec1 = async function () { let result = await longTimeTask(); console.log('result after long time ===>', result); } // 查看函数内部执行顺序 exec1(); // => 等了 xx 年,终于回信了 // => result after long time ===> Object {msg: "task done"} //查看函数总体返回值 console.log(exec1()); // => Promise {[[PromiseStatus]]: "pending",...} // => 同上
以上 2 步执行,清晰的证明了 exec1 函数体内是同步、逐行逐行执行的,即先执行完异步操作,然后进行 console.log() 打印。而 exec1() 的执行结果就直接是一个 Promise,因为它最先会蹦出来一串 Promise ...,然后才是 exec1 函数的内部执行日志。
因此,所有验证,完全符合 整体是一个异步函数,内部整个是同步的 的总结。
await 如何执行其后语句?
回到 await ,看看它是如何执行其后边的语句的。假设:让 longTimeTask() 后边直接带 then() 回调,分两种情况:
1)then() 中不再返回任何东西
2) then() 中继续手动返回另一个 promise
const exec2 = async function () { let result = await longTimeTask().then((res) => { console.log('then ===>', res.msg); res.msg = `${res.msg} then refrash message`; // 注释掉这条 return 或 手动返回一个 promise return Promise.resolve(res); }); console.log('result after await ===>', result.msg); } exec2(); // => 情况一 TypeError: Cannot read property 'msg' of undefined // => 情况二 正常
首先,longTimeTask() 加上再多得 then() 回调,也不过是放在了它的回调列队 queue 里了。也就是说,await 命令之后始终是一条 表达式语句,只不过上述代码书写方式比较让人迷惑。(比较好的实践建议是,将 longTimeTask 方法身后的 then() 移入 longTimeTask 函数体封装起来)
其次,手动返回另一个 promise 和什么也不返回,关系到 longTimeTask() 方法最终 resolve 出去的内容不一样。换句话说,await 命令会提取其后边的promise 的 resolve 结果,进而直接导致 result 的不同。
值得强调的是,await 命令只认 resolve 结果,对 reject 结果报错。不妨用以下的 return 语句替换上述 return 进行验证。
return Promise.reject(res);
最后
其实,关于异步编程还有很多可以梳理的,比如跨模块的异步编程、异步的单元测试、异步的错误处理以及什么是好的实践。All in all, 限于篇幅,不在此汇总了。最后,async + await 确实是一个很优雅的方案。
相关推荐:
以上がES6 非同期 + 待機同期/非同期ソリューションの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。