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.resolve(undefine)을 반환할 것이라고 상상하기 쉽습니다.
기다리지 않는 Promise의 특징을 생각해 보세요. 따라서 기다리지 않고 비동기 함수를 실행하면 즉시 실행되고 Promise 객체가 반환되며 후속 명령문을 차단하지 않습니다. 이는 Promise 객체를 반환하는 일반 함수와 다르지 않습니다.
그럼 다음 핵심은 wait 키워드입니다.
awaitwaiting이 정확히 무엇인가요?
일반적으로 Wait는 비동기 함수가 완료되기를 기다리는 것으로 알려져 있습니다. 그러나 구문에 따르면 Wait는 표현식을 기다리고 있으며 이 표현식의 평가 결과는 Promise 객체 또는 다른 값입니다(즉, 특별한 제한이 없습니다).
비동기 함수는 Promise 객체를 반환하므로, wait를 사용하여 비동기 함수의 반환 값을 기다릴 수 있습니다. 이는 또한 wait가 비동기 함수를 기다리고 있다고 말할 수도 있지만, 다음 사항이 분명해야 합니다. 실제로는 반환 값을 기다리고 있습니다. Wait는 Promise 객체를 기다리는 데 사용될 뿐만 아니라 모든 표현식의 결과를 기다릴 수 있습니다. 따라서 실제로는 Wait 뒤에 일반 함수 호출이나 직접 수량을 사용할 수 있습니다. 따라서 다음 예제는
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가 기다리고 싶은 것을 기다립니다. 아니면 다른 가치, 그리고 모직물? 먼저 Wait는 표현식을 형성하는 데 사용되는 연산자라는 점을 말씀드리고 싶습니다. Wait 표현식의 결과는 기다리고 있는 내용에 따라 달라집니다.
기다리는 것이 Promise 개체가 아닌 경우 대기 표현식의 결과는 기다리는 것입니다.
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
看到上面的阻塞一词,心慌了吧……放心,这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。
async/await 帮我们干了啥
作个简单的比较
上面已经说明了 async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。
现在举例,用 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 方案的死穴