前言
最近准备学习 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 a
- async 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 a
- async 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
来源: http://www.jianshu.com/p/2f5f37cb2869