ホームページ > 記事 > ウェブフロントエンド > JavaScript の async/await を理解する
Node 7 のリリースにより、非同期プログラミングの究極のソリューションと言われている async/await を勉強し始める人が増えています。私がこの一連のキーワードを初めて見たのは、JavaScript 言語ではなく、C# 5.0 の構文でした。 C# の async/await は .NET Framework 4.5 以降で使用する必要があるため、XP システムとの互換性を保つために、私たちが開発したソフトウェアは .NET Framework 4.0 以降を使用できないことをしばらく残念に思いました。
C# であっても JavaScript であっても、async/await は優れた機能であり、非常に優れた糖衣構文でもあります。 C# の async/await 実装は Task または Task24243fe46d427c058a18f95dab2c319e クラスから分離できず、JavaScript の async/await 実装も Promise から分離できません。
ここで、C# と .NET Framework のことは脇に置いて、JavaScript の async/await の学習に集中してください。
async と await が何をしているのか
どの名前にも意味があります。まず文字通りに理解してください。 async は「asynchronous」の略で、await は async wait の略と考えることができます。したがって、async は関数が非同期であることを宣言するために使用され、await は非同期メソッドの実行が完了するのを待つために使用されることをよく理解する必要があります。
興味深い文法規則もあります。await は非同期関数でのみ使用できます。それでは、注意深い友人は、await が非同期関数内でのみ使用できる場合、この非同期関数はどのように呼び出すべきなのでしょうか?
await を通じて非同期関数を呼び出す必要がある場合は、呼び出しを別の非同期関数でラップする必要があります。関数を実行すると...無限ループに入り、決して突破口が開かれません...
async 関数の呼び出しを待機する必要がない場合、async はどのような役割を果たしますか?
async はどのような役割を果たしますか
この質問の鍵は、async 関数がその戻り値をどのように処理するかということです
もちろん、return ステートメントを通じて直接必要な値を返せることを望みますが、その場合は、次のようになります。待つこととは何の関係もありません。それで、コードを書いて、それが何を返すか試してみてください:
async function testAsync() { return "hello async"; } const result = testAsync(); console.log(result);
出力を見て突然気づきました - 出力は Promise オブジェクトです。
c:\var\test> node --harmony_async_await . Promise { 'hello async' }
つまり、async 関数は Promise オブジェクトを返します。この情報はドキュメントからも入手できます。非同期関数 (関数ステートメント、関数式、ラムダ式を含む) は Promise オブジェクトを返します。関数内で直接値が返された場合、async は Promise.resolve() を通じてその直接値を Promise オブジェクトにカプセル化します。
async 関数は Promise オブジェクトを返すので、最外層が戻り値を取得するために await を使用できない場合は、もちろん、元の方法を使用する必要があります: then() チェーンを使用してこの Promise オブジェクトを処理します。次のようにします
testAsync().then(v => { console.log(v); // 输出 hello async });
さて考えてみましょう振り返って考えてみると、async 関数が値を返さなかったらどうなるでしょうか? Promise.resolve(unknown) を返すと考えるのは簡単です。
Promise の特性について考えてみましょう - 待機がないため、await なしで非同期関数を実行すると、すぐに実行され、Promise オブジェクトが返され、後続のステートメントがブロックされることはありません。これは、Promise オブジェクトを返す通常の関数と何ら変わりません。
次に重要なポイントは await キーワードです。
await 何を待っていますか? 一般的に、await は非同期関数が完了するのを待っていると考えられています。ただし、構文によれば、await は式を待っており、この式の評価結果は Promise オブジェクトまたはその他の値になります (つまり、特別な制限はありません)。
async 関数は Promise オブジェクトを返すため、await を使用して async 関数の戻り値を待つことができます。これは await が async 関数を待っているとも言えますが、実際に待っていることを明確にする必要があります戻り値の場合。 await は Promise オブジェクトを待機するために使用されるだけでなく、任意の式の結果を待機することもできるので、実際には await の後に通常の関数呼び出しまたは直接量を続けることができることに注意してください。したがって、次の例は正しく実行できますfunction getSomething() { return "something"; } async function testAsync() { return Promise.resolve("hello async"); } async function test() { const v1 = await getSomething(); const v2 = await testAsync(); console.log(v1, v2); } test();await は、待ちたいものを取得するまで待機します。その後、何をしますか?await は、待ちたいもの、Promise オブジェクト、またはその他の値を取得します。その後、何をしますか?最初に言っておきますが、await は式を形成するために使用される演算子です。await 式の結果は、何を待っているかによって異なります。 待機しているものが Promise オブジェクトではない場合、await 式の結果が待機しているものになります。 Promise オブジェクトを待機している場合、await は次のコードをブロックし、Promise オブジェクトが解決されるのを待ってから、await 式の結果として解決された値を取得します。 上記の「ブロッキング」という単語を見ると、慌てるかもしれません...心配しないでください。非同期関数で await を使用する必要があるのはこのためです。非同期関数呼び出しはブロックを引き起こしません。その内部のブロックはすべて Promise オブジェクトにカプセル化され、非同期に実行されます。
async/await は何をしてくれたのですか
簡単な比較
async は後続の関数 (関数式または Lambda) の戻り値を Promise オブジェクトにカプセル化し、await がそれをカプセル化することは上で説明しました。この Promise が完了し、解決された結果が返されるまで待ちます。现在举例,用 setTimeout 模拟耗时的异步操作,先来看看不用 async/await 会怎么写
function takeLongTime() { return new Promise(resolve => { setTimeout(() => resolve("long_time_value"), 1000); }); } takeLongTime().then(v => { console.log("got", v); });
如果改用 async/await 呢,会是这样
function takeLongTime() { return new Promise(resolve => { setTimeout(() => resolve("long_time_value"), 1000); }); } async function test() { const v = await takeLongTime(); console.log(v); } test();
眼尖的同学已经发现 takeLongTime() 没有申明为 async。实际上,takeLongTime() 本身就是返回的 Promise 对象,加不加 async结果都一样,如果没明白,请回过头再去看看上面的“async 起什么作用”。
又一个疑问产生了,这两段代码,两种方式对异步调用的处理(实际就是对 Promise 对象的处理)差别并不明显,甚至使用 async/await 还需要多写一些代码,那它的优势到底在哪?
async/await 的优势在于处理 then 链
单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作:
/** * 传入参数 n,表示这个函数执行的时间(毫秒) * 执行的结果是 n + 200,这个值将用于下一步骤 */ function takeLongTime(n) { return new Promise(resolve => { setTimeout(() => resolve(n + 200), n); }); } function step1(n) { console.log(`step1 with ${n}`); return takeLongTime(n); } function step2(n) { console.log(`step2 with ${n}`); return takeLongTime(n); } function step3(n) { console.log(`step3 with ${n}`); return takeLongTime(n); }
现在用 Promise 方式来实现这三个步骤的处理
function doIt() { console.time("doIt"); const time1 = 300; step1(time1) .then(time2 => step2(time2)) .then(time3 => step3(time3)) .then(result => { console.log(`result is ${result}`); console.timeEnd("doIt"); }); } doIt(); // c:\var\test>node --harmony_async_await . // step1 with 300 // step2 with 500 // step3 with 700 // result is 900 // doIt: 1507.251ms
输出结果 result 是 step3() 的参数 700 + 200 = 900。doIt() 顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的结果一致。
如果用 async/await 来实现呢,会是这样
async function doIt() { console.time("doIt"); const time1 = 300; const time2 = await step1(time1); const time3 = await step2(time2); const result = await step3(time3); console.log(`result is ${result}`); console.timeEnd("doIt"); } doIt();
结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样
还有更酷的
现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。
function step1(n) { console.log(`step1 with ${n}`); return takeLongTime(n); } function step2(m, n) { console.log(`step2 with ${m} and ${n}`); return takeLongTime(m + n); } function step3(k, m, n) { console.log(`step3 with ${k}, ${m} and ${n}`); return takeLongTime(k + m + n); }
这回先用 async/await 来写:
async function doIt() { console.time("doIt"); const time1 = 300; const time2 = await step1(time1); const time3 = await step2(time1, time2); const result = await step3(time1, time2, time3); console.log(`result is ${result}`); console.timeEnd("doIt"); } doIt(); // c:\var\test>node --harmony_async_await . // step1 with 300 // step2 with 800 = 300 + 500 // step3 with 1800 = 300 + 500 + 1000 // result is 2000 // doIt: 2907.387ms
除了觉得执行时间变长了之外,似乎和之前的示例没啥区别啊!别急,认真想想如果把它写成 Promise 方式实现会是什么样子?
function doIt() { console.time("doIt"); const time1 = 300; step1(time1) .then(time2 => { return step2(time1, time2) .then(time3 => [time1, time2, time3]); }) .then(times => { const [time1, time2, time3] = times; return step3(time1, time2, time3); }) .then(result => { console.log(`result is ${result}`); console.timeEnd("doIt"); }); } doIt();
有没有感觉有点复杂的样子?那一堆参数处理,就是 Promise 方案的死穴—— 参数传递太麻烦了,看着就晕!
就目前来说,已经理解 async/await 了吧?但其实还有一些事情没提及——Promise 有可能 reject 啊,怎么处理呢?如果需要并行处理3个步骤,再等待所有结果,又该怎么处理呢?