Mit der Veröffentlichung von Node 7 beginnen immer mehr Menschen, sich mit Async/Await zu beschäftigen, was als die ultimative Lösung für asynchrone Programmierung gilt. Das erste Mal, dass ich diesen Schlüsselwortsatz sah, war nicht in der JavaScript-Sprache, sondern in der Syntax von C# 5.0. Async/await von C# muss in .NET Framework 4.5 oder höher verwendet werden, daher war ich eine Weile traurig – um mit XP-Systemen kompatibel zu sein, darf die von uns entwickelte Software kein .NET Framework höher als 4.0 verwenden.
Ob in C# oder JavaScript, Async/Await sind großartige Funktionen und außerdem ein sehr süßer syntaktischer Zucker. Die Async/Await-Implementierung von C# ist untrennbar mit der Task- oder Task
Lassen Sie nun C# und .NET Framework beiseite und konzentrieren Sie sich auf das Studium von JavaScripts Async/Await.
Was Async und Wait bewirken
Jeder Name ist bedeutungsvoll, verstehen Sie ihn zunächst wörtlich. Async ist die Abkürzung für „asynchron“, und Warten kann als Abkürzung für Async Wait angesehen werden. Es sollte also klar sein, dass async verwendet wird, um zu deklarieren, dass eine Funktion asynchron ist, während „await“ verwendet wird, um darauf zu warten, dass eine asynchrone Methode die Ausführung abschließt.
Es gibt auch eine interessante grammatikalische Regel: „await“ darf nur in asynchronen Funktionen vorkommen. Dann werden vorsichtige Freunde eine Frage haben: Wenn „Warten“ nur in asynchronen Funktionen angezeigt werden kann, wie soll diese asynchrone Funktion aufgerufen werden?
Wenn Sie eine asynchrone Funktion über „Warten“ aufrufen müssen, muss dies außerhalb des Aufrufs erfolgen Wickeln Sie eine weitere asynchrone Funktion ein und treten Sie dann ... in eine Endlosschleife ein, ohne jemals einen Durchbruch zu erzielen ...
Wenn die asynchrone Funktion nicht auf den Aufruf warten muss, welche Rolle spielt dann die asynchrone Funktion?
Welche Rolle spielt Async?
Der Schlüssel zu dieser Frage ist, wie die Async-Funktion mit ihrem Rückgabewert umgeht!
Natürlich hoffen wir, dass sie was zurückgeben kann Wir möchten den gewünschten Wert direkt über die Return-Anweisung erhalten. Wenn dies jedoch der Fall ist, scheint es nichts mit Warten zu tun zu haben. Versuchen Sie also, einen Code zu schreiben, um zu sehen, was er zurückgibt:
async function testAsync() { return "hello async"; } const result = testAsync(); console.log(result);
Wenn Sie die Ausgabe sehen, stellen Sie plötzlich fest, dass es sich bei der Ausgabe um ein Promise-Objekt handelt.
c:\var\test> node --harmony_async_await . Promise { 'hello async' }
Die asynchrone Funktion gibt also ein Promise-Objekt zurück. Diese Informationen können auch der Dokumentation entnommen werden. Asynchrone Funktionen (einschließlich Funktionsanweisungen, Funktionsausdrücke und Lambda-Ausdrücke) geben ein Promise-Objekt zurück. Wenn in der Funktion ein direkter Wert zurückgegeben wird, kapselt async den direkten Wert über Promise.resolve() in ein Promise-Objekt.
Die asynchrone Funktion gibt ein Promise-Objekt zurück. Wenn die äußerste Schicht also nicht „await“ verwenden kann, um ihren Rückgabewert zu erhalten, sollten wir natürlich die ursprüngliche Methode verwenden: then()-Kette, um dieses Promise-Objekt zu verarbeiten, einfach so
testAsync().then(v => { console.log(v); // 输出 hello async });
Denken Sie jetzt zurück und denken Sie darüber nach: Was ist, wenn die asynchrone Funktion keinen Wert zurückgibt? Es ist leicht zu glauben, dass sie Promise.resolve (undefiniert) zurückgibt.
Denken Sie an die Eigenschaften von Promise – kein Warten. Wenn Sie also eine asynchrone Funktion ohne Warten ausführen, wird sie sofort ausgeführt, gibt ein Promise-Objekt zurück und blockiert niemals nachfolgende Anweisungen. Dies unterscheidet sich nicht von einer normalen Funktion, die ein Promise-Objekt zurückgibt.
Dann ist der nächste wichtige Punkt das Schlüsselwort „await“.
Worauf genau wartet das Warten?
Im Allgemeinen geht man davon aus, dass das Warten auf den Abschluss einer asynchronen Funktion wartet. Gemäß der Syntax wartet Warten jedoch auf einen Ausdruck, und das Auswertungsergebnis dieses Ausdrucks ist ein Promise-Objekt oder ein anderer Wert (mit anderen Worten, es gibt keine besonderen Einschränkungen).
Da die asynchrone Funktion ein Promise-Objekt zurückgibt, kann „await“ verwendet werden, um auf den Rückgabewert einer asynchronen Funktion zu warten – man kann auch sagen, dass „await“ auf die asynchrone Funktion wartet, aber das muss klar sein es wartet tatsächlich auf einen Rückgabewert. Beachten Sie, dass „Await“ nicht nur zum Warten auf Promise-Objekte verwendet wird, sondern auch auf das Ergebnis eines beliebigen Ausdrucks. Daher können auf „Await“ tatsächlich normale Funktionsaufrufe oder direkte Mengen folgen. So kann das folgende Beispiel korrekt ausgeführt werden:
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 wartet auf das, worauf es warten möchte, und dann
await wartet auf das, worauf es warten möchte, ein Promise-Objekt. oder ein anderer Wert, und dann muss ich zunächst sagen, dass „await“ ein Operator ist, der zum Bilden von Ausdrücken verwendet wird. Das Ergebnis des „await expression“ hängt davon ab, worauf er wartet.
Wenn das, worauf es wartet, kein Promise-Objekt ist, ist das Ergebnis des Warteausdrucks das, worauf es wartet.
Wenn auf ein Promise-Objekt gewartet wird, ist „await“ beschäftigt. Es blockiert den folgenden Code, wartet auf die Auflösung des Promise-Objekts und erhält dann den aufgelösten Wert als Ergebnis des „await“-Ausdrucks.
Wenn Sie oben das Wort „Blockierung“ sehen, geraten Sie in Panik ... Keine Sorge, aus diesem Grund muss „await“ in asynchronen Funktionen verwendet werden. Asynchrone Funktionsaufrufe verursachen keine Blockierung. Alle darin enthaltenen Blockierungen werden in einem Promise-Objekt gekapselt und asynchron ausgeführt.
Was hat async/await für uns bewirkt?
Machen Sie einen einfachen Vergleich
Oben wurde erklärt, dass async die nachfolgende Funktion konvertieren wird ( Funktion Der Rückgabewert eines Ausdrucks oder Lambda wird in ein Promise-Objekt gekapselt, und Warten wartet auf den Abschluss dieses Versprechens und gibt das aufgelöste Ergebnis zurück.
现在举例,用 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个步骤,再等待所有结果,又该怎么处理呢?