前言
最近准备学习Koa,因此将JavaScript的异步操作又理了一遍,发现还是async await写起来比较直白,不愧是现在JavaScript异步操作的终极解决方案。
一、Async/Await的初识
Async/Await的含义
Async - 定义异步函数(async function someName(){...})
自动把函数转换为 Promise
当调用异步函数时,函数返回值会被 resolve 处理
异步函数内部可以使用 await
Await - 暂停异步函数的执行 (var result = await someAsyncCall())
当使用在 Promise 前面时,await 等待 Promise 完成,并返回 Promise 的结果
await 只能和 Promise 一起使用,不能和 callback 一起使用
await 只能用在 async 函数中
Async/Await 和 Generator
async 函数是ES6所提出的,本质上是Generator函数的语法糖,但它在以下四点做了长足的改进:
内置执行器:Generator 函数的执行必须依靠执行器,而 async 函数则自带执行器,调用的方式和普通函数调用一样。
语义化:较之于Generator 函数的 * 和 yield,async/await无疑是更为语义化。
返回的值是Promise: async 函数返回值是Promise对象,及哦啊哈子与Generator 函数所返回的Lterator对象更加方便,可以直接使用 then() 方法进行链式调用。
适用范围更广: co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise对象。而 async 函数的 await 命令后面则可以是 Promise 或者 原始类型的值。
二、async 语法
首先 async 函数返回一个 Promise 对象,也就是说 async 函数内部 return 返回的值,会成为 then 方法回调函数的参数,等同于 return Promise.resolve(value)。
async function f() { return 'hello async'}; f().then( (v) => console.log(v)) // hello async
其次 async 函数所返回的 Promise 对象,必须等到内部所有的 await 命令的 Promise 对象执行完成后,才会发生状态改变。
const delay = timeout => new Promise(resolve=> setTimeout(resolve, console.log(timeout) timeout))async function f(){ await delay(1000) await delay(2000) await delay(3000) return 'end'} f().then(v => console.log(v)) // 需要等待6秒后才会输出"end"
正常情况下,await 命令后面跟着的是 Promise ,如果不是的话,也会被转换成一个 立即 resolve 的 Promise。
async function f() { return await 1}; f().then( (v) => console.log(v)) // 1
最后如果 async 函数内部抛出异常,则会导致返回的 Promise 对象状态变为 reject 状态。抛出的错误而会被 catch 方法回调函数接收到。
async function e(){ throw new Error('error'); } e().then(v => console.log(v)) .catch( e => console.log(e));
三、async和其他异步操作的比较
直接上代码:
首先定义一个可以获取 github user 的 fetch 方法:
function fetchUser() { return new Promise((resolve, reject) => { fetch('https://api.github.com/users/srtian') .then((data) => { resolve(data.json()) }, (error) => { reject(error) }) }) }
Promise
function getUserByPromise() { fetchUser() .then((data) => { console.log(data) }, (error) => { console.log(error) } } getUserByPromise();
这样看起来使用 Promise 来进行异步操作好像非常不错,但有个问题是,一旦then变多了,代码将会变得非常冗长和复杂,且语义化不明显,代码流程不能很好的表示执行的流程。
Generator
function *fetchUserByGenerator() { const user = yield fetchUser() return user }const g = fetchUserByGenerator()const result = g.next().value result.then((v) => { console.log(v) }, (error) => { console.log(error) })
Generator 的方式解决了 Promise 的一些问题,流程更加直观、语义化。但是 Generator 的问题在于,函数的执行需要依靠执行器,每次都需要通过 g.next() 的方式去执行。而且虽然语义上有所进步,但 * 和 yield 明显还是不能满足我们对语义化直观观察的需要。
async
async function getUserByAsync(){ let user = await fetchUser() return user } getUserByAsync() .then(v => console.log(v))
而async/await则很好的解决了上面两种异步操作的一些问题。首先使用同步的方法来写异步,代码非常清晰直观;其次使用async和await,在语义上非常好,一眼就能看出代码执行的顺序;最后 asunc 函数自带执行器,执行的时候无需手动加载。
四、其他
错误处理
除了上面的那些东西,Async 函数的错误处理也需要额外注意:
let aasync function f() { await Promise.reject('error') a = await 1} f().then(v => console.log(a))
上面的 a = await 1 没有执行,这是由于在async函数中,只要有一个await出现 reject 状态,那么后面的await都不会被执行。所以我们就需要使用try/catch来解决这个问题:
let aasync function correct() { try { await Promise.reject('error') } catch (error) { console.log(error) } a = await 1 return a } correct().then(v => console.log(a)) // 1
并行
当我们需要使用并行进行加载时,使用async可能可以实现,但这是很低效的比如这样:
await a()await b()await c()await d()
换成回调就是这样的:
a(() => { b(() => { c(() => { d() }) }) })
然而我们发现,原始代码中,函数 c 可以与 a 同时执行,但 async/await 语法会让我们倾向于在 b 执行完后,再执行 c。
所以我们其实可以这样:
async function ab() { await a() b() }async function cd() { await c() d() }Promise.all([ab(), cd()])
参考资料:
https://segmentfault.com/a/1190000014753495
https://juejin.im/post/596e142d5188254b532ce2da
作者:Srtian
链接:https://www.jianshu.com/p/2f5f37cb2869