关注[前端小讴],阅读更多原创技术文章
async/await
),是 ES6 期约模式在 ECMAScript 函数中的应用相关代码 →
async
和await
async
关键字用于声明异步函数,可用在函数声明、函数表达式、箭头函数和方法上async function foo() {} // 用在函数声明 let bar = async function () {} // 用在函数表达式 let baz = async () => {} // 用在箭头函数 class Qux { async qux() {} // 用在方法 }
async
关键字让函数具有异步特性,代码仍同步求值,参数或闭包也具有普通 JS 函数的正常行为async function foo() { console.log(1) } foo() console.log(2) /* 1,foo()函数先被求值 2 */
return
返回的值,会被Promise.resolve()
包装成期约对象,调用异步函数始终返回该期约对象
return
关键字返回的是实现thenable
接口的对象(callback
、期约),该对象由提供给then()
的处理程序解包return
关键字返回的是常规的值,返回值被当作已解决的期约(无return
关键字,返回值被当作 undefined)async function foo() { return 'foo' // 返回原始值 } console.log(foo()) // Promise {<fulfilled>: "foo"},被当作已解决的期约 foo().then((result) => console.log(result)) // 'foo' async function bar2() { return ['bar'] // 返回没有实现thenable接口的对象 } console.log(bar2()) // Promise {<fulfilled>: ['bar']},被当作已解决的期约 bar2().then((result) => console.log(result)) // ['bar'] async function baz2() { const thenable = { then(callback) { callback('baz') }, } return thenable // 返回实现了thenable接口的非期约对象 } console.log(baz2()) // Promise {<pending>} baz2().then((result) => console.log(result)) // 'baz',由then()解包 async function qux() { return Promise.resolve('qux') // 返回解决的期约 } console.log(qux()) // Promise {<pending>} qux().then((result) => console.log(result)) // 'qux',由then()解包 async function rejectQux() { return Promise.reject('qux') // 返回拒绝的期约 } console.log(rejectQux()) // Promise {<pending>} rejectQux().then(null, (result) => console.log(result)) // 'qux',由then()解包 // Uncaught (in promise) qux rejectQux().catch((result) => console.log(result)) // 'qux',由catch()解包
async function foo() { console.log(1) throw 3 } foo().catch((result) => console.log(result)) // 给返回的期约添加拒绝处理程序 console.log(2) /* 1,foo()函数先被求值 2 3 */
async function foo() { Promise.reject(3) // 拒绝的期约(非返回) } foo().catch((result) => console.log(result)) // catch()方法捕获不到 // Uncaught (in promise) 3,浏览器消息队列捕获
await
关键字可以暂停异步函数代码执行,等待期约解决let p = new Promise((resolve, reject) => { setTimeout(resolve, 1000, 3) }) p.then((x) => console.log(x)) // 3 // 用async/await重写 async function foo() { let p = new Promise((resolve, reject) => { setTimeout(resolve, 1000, 3) }) console.log(await p) } foo() // 3
await
会尝试解包对象的值(与yield
类似),然后将该值传给表达式,而后异步恢复执行异步函数async function foo() { console.log(await Promise.resolve('foo')) // 将期约解包,再将值传给表达式 } foo() async function bar2() { return await Promise.resolve('bar') } bar2().then((res) => console.log(res)) // 'bar' async function baz2() { await new Promise((resolve, reject) => { setTimeout(resolve, 1000) }) console.log('baz') } baz2() // 'baz'(1000毫秒后)
await
根据等待的值,执行不同的操作
thenable
接口的对象(callback
、期约),该对象由await
来解包await
来解包)async function foo() { console.log(await 'foo') // 等待原始值,被当作已解决的期约Promise.resolve('foo'),再由await解包 } foo() // 'foo' async function bar2() { console.log(await ['bar']) // 等待值是没有实现thenable接口的对象,被当作已解决的期约再由await解包 } bar2() // ["bar"] async function baz2() { const thenable = { then(callback) { callback('baz') }, } console.log(await thenable) // 等待值是实现了thenable接口的非期约对象,由await解包 } baz2() // 'baz' async function qux() { console.log(await Promise.resolve('qux')) // 等待值是解决的期约 } qux() // 'qux'
async function foo() { console.log(1) await (() => { throw 3 // 抛出错误的同步操作 })() } foo().catch((result) => console.log(result)) // 给返回的期约添加拒绝处理程序 console.log(2) /* 1 2 3 */
await
,会释放错误值(将拒绝期约返回)async function foo() { console.log(1) await Promise.reject(3) // 对拒绝的期约使用await,将其返回(后续代码不再执行) console.log(4) // 不执行 } foo().catch((result) => console.log(result)) // 给返回的期约添加拒绝处理程序 console.log(2) /* 1 2 3 */
<script>
标签或模块)中使用async function foo() { console.log(await Promise.resolve(3)) // 必须在异步函数中使用 } foo() // 3 ;(async function () { console.log(await Promise.resolve(3)) // 3,立即调用的异步函数表达式 })() const syncFn = async () => { console.log(await Promise.resolve(3)) // 在箭头函数中使用,箭头函数前一样要加async } syncFn() // 3 function foo() { // console.log(await Promise.resolve(3)) // 不允许在同步函数中使用 } async function foo() { // function bar() { // console.log(await Promise.resolve(3)) // 错误:异步函数不会扩展到嵌套函数 // } async function bar() { console.log(await Promise.resolve(3)) // 需要在bar前加async } }
async/await
真正起作用的是await
(async
只是标识符)
await
关键字,会记录在哪里暂停执行await
右边的值可以用时,JS 向消息队列推送任务,该任务恢复异步函数的执行await
右边跟着一个立即可用的值,函数也会暂停,且其余部分会被异步求值// async只是标识符 async function foo() { console.log(2) } console.log(1) foo() console.log(3) /* 1 2 3 */ // 遇到await -> 记录暂停 -> await右边的值可用 -> 恢复执行异步函数 async function foo() { console.log(2) await null // 暂停,且后续操作变为异步 // 为立即可用的值null向消息队列中添加一个任务 console.log(4) } console.log(1) foo() console.log(3) /* 1 2 3 4 */
await
后面是一个期约,则会有两个任务被添加到消息队列并被异步求值
await
后面是期约的情况做过 1 次修改,await Promise.resolve()
不再生成 2 个异步任务,而只是 1 个async function foo() { console.log(2) console.log(await Promise.resolve(8)) console.log(9) } async function bar2() { console.log(4) console.log(await 6) console.log(7) } console.log(1) foo() console.log(3) bar2() console.log(5) /* 书本顺序:1 2 3 4 5 6 7 8 9 浏览器顺序:1 2 3 4 5 8 9 6 7(tc39做过1次修改) */
JAVA
中Thread.sleep()
的函数,在程序中加入非阻塞的暂停function sleep(delay) { return new Promise((resolve) => setTimeout(resolve, delay)) // 设定延迟,延迟后返回一个解决的期约 } async function foo() { const t0 = Date.now() await sleep(1500) // 暂停约1500毫秒 console.log(Date.now() - t0) } foo() // 1507
async function randomDelay(id) { const delay = Math.random() * 1000 // 随机延迟0-1000毫秒 return new Promise((resolve) => setTimeout(() => { console.log(`${id} finished`) resolve() }, delay) ) } async function foo() { const t0 = Date.now() await randomDelay(0) await randomDelay(1) await randomDelay(2) await randomDelay(3) await randomDelay(4) console.log(`${Date.now() - t0} ms elapsed`) } foo() /* 0 finished 1 finished 2 finished 3 finished 4 finished 3279 ms elapsed */ // 用for循环重写 async function foo() { const t0 = Date.now() for (let i = 0; i < 5; i++) { await randomDelay(i) } console.log(`${Date.now() - t0} ms elapsed`) } foo() /* 0 finished 1 finished 2 finished 3 finished 4 finished 3314 ms elapsed */
async function foo() { const t0 = Date.now() // 一次性初始化所有期约 const p0 = randomDelay(0) const p1 = randomDelay(1) const p2 = randomDelay(2) const p3 = randomDelay(3) const p4 = randomDelay(4) // 分别等待结果,延迟各不相同 await p0 await p1 await p2 await p3 await p4 console.log(`${Date.now() - t0} ms elapsed`) } foo() /* 4 finished 3 finished 1 finished 0 finished 2 finished 870 ms elapsed,大幅度降低总耗时 */ // 用数组和for循环再次包装 async function foo() { const t0 = Date.now() const promises = Array(5) .fill(null) .map((item, i) => randomDelay(i)) for (const p of promises) { await p } console.log(`${Date.now() - t0} ms elapsed`) } foo() /* 1 finished 3 finished 0 finished 4 finished 2 finished 806 ms elapsed */
await
按顺序收到每个期约的值async function randomDelay(id) { const delay = Math.random() * 1000 // 随机延迟0-1000毫秒 return new Promise((resolve) => setTimeout(() => { console.log(`${id} finished`) resolve(id) }, delay) ) } async function foo() { const t0 = Date.now() const promises = Array(5) .fill(null) .map((item, i) => randomDelay(i)) for (const p of promises) { console.log(`awaited ${await p}`) } console.log(`${Date.now() - t0} ms elapsed`) } foo() /* 1 finished 4 finished 0 finished awaited 0 awaited 1 2 finished awaited 2 3 finished awaited 3 awaited 4 833 ms elapsed */
async/await
做期约连锁function addTwo(x) { return x + 2 } function addThree(x) { return x + 3 } function addFive(x) { return x + 5 } async function addTen(x) { for (const fn of [addTwo, addThree, addFive]) { x = await fn(x) } return x } addTen(9).then((res) => console.log(res)) // 19
async function addTwo(x) { return x + 2 } async function addThree(x) { return x + 3 } async function addFive(x) { return x + 5 } addTen(9).then((res) => console.log(res)) // 19
function fooPromiseExecutor(resolve, reject) { setTimeout(reject, 1000, 'bar') } function foo() { new Promise(fooPromiseExecutor) } foo() /* Uncaught (in promise) bar setTimeout (async) // 错误信息包含嵌套函数的标识符 fooPromiseExecutor // fooPromiseExecutor函数已返回,不应该在栈追踪信息中看到 foo */
async function foo() { await new Promise(fooPromiseExecutor) } foo() /* Uncaught (in promise) bar foo async function (async) foo */