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 및 Wait의 기능
모든 이름은 의미가 있으므로 먼저 문자 그대로 이해하세요. async는 "asynchronous"의 약자이고, wait는 async wait의 약자라고 생각하면 됩니다. 따라서 async는 함수가 비동기임을 선언하는 데 사용되는 반면, wait는 비동기 메서드의 실행이 완료될 때까지 기다리는 데 사용된다는 점을 잘 이해해야 합니다.
또한 흥미로운 문법 규칙이 있는데, Wait는 비동기 함수에만 나타날 수 있다는 것입니다. 그러면 조심스러운 친구들은 의문이 생길 것입니다. Wait가 비동기 함수에서만 나타날 수 있다면 이 비동기 함수는 어떻게 호출해야 할까요?
await를 통해 비동기 함수를 호출해야 한다면 호출 외부는 반드시 호출되어야 합니다. 다른 비동기 함수를 래핑한 다음...무한 루프에 들어가 돌파구를 찾지 못합니다...
비동기 함수가 호출을 기다릴 필요가 없다면 비동기는 어떤 역할을 합니까?
비동기의 역할
이 질문의 핵심은 비동기 함수가 반환 값을 처리하는 방법입니다!
물론 우리는 비동기 함수가 무엇을 반환할 수 있기를 바랍니다. return문을 통해 직접적으로 원하는 값을 원하는데, 이 경우에는 wait와는 관련이 없는 것 같습니다. 따라서 코드를 작성하여 무엇을 반환할지 확인해 보세요.
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 객체를 반환합니다. 이 정보는 설명서에서도 얻을 수 있습니다. 비동기 함수(함수 명령문, 함수 표현식 및 Lambda 표현식 포함)는 Promise 객체를 반환합니다. 함수에 직접 값이 반환되면 비동기는 Promise.resolve()를 통해 직접 값을 Promise 객체로 캡슐화합니다.
비동기 함수는 Promise 객체를 반환하므로 가장 바깥쪽 레이어가 반환 값을 얻기 위해 대기를 사용할 수 없는 경우 물론 원래 방법인 then() 체인을 사용하여 이 Promise 객체를 처리해야 합니다.
testAsync().then(v => { console.log(v); // 输出 hello async });기다리지 않는 Promise의 특징을 생각해 보세요. 따라서 기다리지 않고 비동기 함수를 실행하면 즉시 실행되고 Promise 객체가 반환되며 후속 명령문을 차단하지 않습니다. 이는 Promise 객체를 반환하는 일반 함수와 다르지 않습니다. 그럼 다음 핵심은 wait 키워드입니다.
awaitwaiting이 정확히 무엇인가요?
일반적으로 Wait는 비동기 함수가 완료되기를 기다리는 것으로 알려져 있습니다. 그러나 구문에 따르면 Wait는 표현식을 기다리고 있으며 이 표현식의 평가 결과는 Promise 객체 또는 다른 값입니다(즉, 특별한 제한이 없습니다).
비동기 함수는 Promise 객체를 반환하므로, wait를 사용하여 비동기 함수의 반환 값을 기다릴 수 있습니다. 이는 또한 wait가 비동기 함수를 기다리고 있다고 말할 수도 있지만, 다음 사항이 분명해야 합니다. 실제로는 반환 값을 기다리고 있습니다. Wait는 Promise 객체를 기다리는 데 사용될 뿐만 아니라 모든 표현식의 결과를 기다릴 수 있습니다. 따라서 실제로는 Wait 뒤에 일반 함수 호출이나 직접 수량을 사용할 수 있습니다. 따라서 다음 예제는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가 기다리고 싶은 것을 기다립니다. 또는 다른 값, 그리고 무엇을 말해야 할까요? 먼저 Wait는 표현식을 형성하는 데 사용되는 연산자입니다. Wait 식의 결과는 기다리고 있는 내용에 따라 달라집니다. 기다리는 것이 Promise 개체가 아닌 경우 대기 표현식의 결과는 기다리는 것입니다. Promise 개체를 기다리고 있는 경우 Wait는 다음 코드를 차단하고 Promise 개체가 해결될 때까지 기다린 다음 Wait 식의 결과로 해결된 값을 가져옵니다. 위의 단어 차단을 보면 당황스러울 수 밖에 없습니다... 걱정하지 마세요. 그렇기 때문에 비동기 함수에서는 wait를 사용해야 합니다. 비동기 함수 호출은 차단을 유발하지 않습니다. 그 내부의 모든 차단은 Promise 개체에 캡슐화되어 비동기식으로 실행됩니다.
async/await가 우리에게 어떤 역할을 했나요?
간단한 비교를 해보세요
위에서 async가 후속 함수( function 표현식 또는 Lambda의 반환 값은 Promise 객체로 캡슐화되며, Wait는 이 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个步骤,再等待所有结果,又该怎么处理呢?