提起异步编程,你们可能会想到事件监听、回调函数、发布订阅、Promise 对象、Generator 函数、async 函数等,本篇主要讲解的是 async 函数,不少人认为它是异步编程的终极解决方案。面试
摘自阮老师的文章:一句话,它就是 Generator 函数的语法糖;也有人说它是 Promise 的语法糖。编程
若是你对 Promise 对象、 Generator 函数不是特别了解的话,建议先看一下阮老师 ECMAScript6 入门中的关于 Promise 对象 和 Generator 函数的介绍。promise
1.async 声明的函数的返回本质上是一个 promise 对象(很重要。。。)浏览器
就是说只要你声明了这个函数是 async,那么内部无论你怎么处理,它的返回确定是个 Promise。bash
async function myAsync () {
return 'hello world'
}
let result = myAsync()
console.log(result)
复制代码
2.async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数异步
myAsync().then((val) => {
console.log(val)
})
复制代码
3.async 函数返回的 Promise 对象,必须等到内部全部 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者抛出错误。async
也就是说,只有 async 函数内部的异步操做执行完,才会执行 then 方法指定的回调函数。异步编程
function getNum () {
return new Promise((resolve) => {
setTimeout(() => {
resolve(1000)
}, 1000)
})
}
async function myAsync () {
let num = await getNum()
return num + 1000
}
myAsync().then((val) => {
console.log(val)
})
复制代码
上面代码中,函数 myAsync 内部有两个操做:获取 num,加 1000 后并返回结果。只有这两个操做所有完成,才会执行 then 方法里面的 console.log(val)。函数
1.正常状况下,await 命令后面是一个 Promise 对象,返回该对象的结果。若是不是 Promise 对象,就直接返回对应的值。ui
2.await 的意思就是让 JavaScript 引擎等待,直到 await 命令执行完毕,而后继续执行 await 命令后面的代码。
3.这个行为不会耗费 CPU 资源,由于引擎能够同时处理其余任务:执行其余脚本,处理事件等。
咱们来看个例子,能够试着写出执行结果。
function myAwait () {
return new Promise((resolve) => {
resolve('hello world!')
})
}
async function myAsync(){
console.log('async begin')
let wait = await myAwait()
console.log(wait)
console.log('async end')
return wait
}
console.log('begin')
let result = myAsync()
console.log(result)
console.log('end')
复制代码
以上结果是在谷歌浏览器下执行的结果,“async end” 在 “hello world!” 以后输出,由于 await 阻塞了 “async end” 的输出,可是 ‘end’ 在 “hello world!”以前输出了,由于 await 只会阻塞 async 函数中 await 后面代码的执行,不会阻塞其余代码的执行。
了解了 async 和 await 后,咱们一块儿来看一个完整的例子。
假如咱们作完一件事,须要分三个步骤,每个步骤都须要上一步的执行结果,咱们分别看一下 Promise 、 Generator 和 async 都是怎么实现的。
/* 花费时间 */
function takeLongTime (n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 1000), 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)
}
复制代码
function doIt () {
let time1 = 1000
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`)
})
}
doIt()
复制代码
/** 执行器
* Generator 函数不能自动执行,咱们须要借助执行器
*/
function run (generator) {
let iterator = generator()
let result = iterator.next()
function step () {
if(!result.done) {
let promise = Promise.resolve(result.value)
promise.then((value) => {
result = iterator.next(value)
step()
}).catch((error) => {
result = iterator.throw(error)
step()
})
}
}
step()
}
function *doIt () {
let time1 = 1000
let time2 = yield step1(time1)
let time3 = yield step2(time2)
let result = yield step3(time3)
console.log(`result is ${result}`)
}
run(doIt)
复制代码
async function doIt () {
let time1 = 1000
let time2 = await step1(time1)
let time3 = await step2(time2)
let result = await step3(time3)
console.log(`result is ${result}`)
}
doIt()
复制代码
三种方法执行结果都以下:
对比以上三种实现方式:
1.因为 Promise 的 then 方法返回的是一个新的 Promise,因此 Promise 能够经过链式调用实现异步编程。
2.async 函数和 Generator 函数就比较有意思了,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,并内置执行器,仅此而已。
3.不难发现,async 的写法更具语义化,而且更加清晰。
1.await 命令后面的 Promise 对象,运行结果多是 rejected,因此最好把 await命令放在 try...catch 代码块中。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另外一种写法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}
复制代码
2.多个 await 命令后面的异步操做,若是不存在继发关系,最好让它们同时触发。
function getA () {
return new Promise((resolve) => {
setTimeout(() => {
resolve('A')
}, 1000)
})
}
function getB () {
return new Promise((resolve) => {
setTimeout(() => {
resolve('B')
}, 1000)
})
}
async function myAsync () {
let A = await getA();
console.log('A: ', A)
let B = await getB();
console.log('B: ', B)
}
myAsync()
复制代码
上面代码中,getA 和 getB 是两个独立的异步操做(即互不依赖),被写成继发关系。这样比较耗时,由于只有 getA 完成之后,才会执行 getB,彻底可让它们同时触发。
// 写法一
async function myAsync () {
let [A, B] = await Promise.all([getA(), getB()])
console.log('A: ', A)
console.log('B: ', B)
}
myAsync()
复制代码
// 写法二
async function myAsync () {
let aPromise = getA()
let bPromise = getB()
let A = await aPromise
let B = await bPromise
console.log('A: ', A)
console.log('B: ', B)
}
myAsync()
复制代码
上面两种写法,getA 和 getB 都是同时触发,这样就会缩短程序的执行时间。
3.await 命令只能用在 async 函数之中,若是用在普通函数,就会报错。
函数前面的关键字 async 有两个做用:
1.让这个函数返回一个 promise 2.容许在函数内部使用 await,这个 await 关键字又让 JavaScript 引擎等待直到 promise 完成,若是有错误,就会抛出异常,不然,就返回结果。
这两个关键字一块儿用就提供了一个通俗易懂的方式来控制异步编程,而且易于读写。
相信你对 Promise、Generator、async 已经有了必定的了解了,若加上 setTimeout,你对代码的执行顺序还很清晰吗?
咱们来看一道写出执行结果的题,相信不少同窗面试的时候都遇到过,是否是很懵逼!!!
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
},0)
async1()
new Promise((resolve) => {
console.log('promise1')
resolve()
}).then(() => {
console.log('promise2')
})
console.log('script end')
复制代码
执行结果(不一样浏览器执行结果可能不一样,下面结果用的谷歌):
请你们谨记执行规则:setTimeout 的优先级最低,没有 async 和 promise 级别高(其实 async 和 promise 是同样的,由于调用 async 方法时就是返回一个 promise 对象),async 和 promise 的 .then 就看谁先进入到的任务队列里面,任务队列里面有先进先出的概念。
按照这个规则,相信你很快就能写出执行结果了。