初探 async await

前言

最近准备学习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

    原文作者:Srtian
    原文地址: https://www.jianshu.com/p/2f5f37cb2869
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞