ECMAScript6-async和await

94 阅读4分钟

async 和 await 是 Generator + Promise 的语法糖。async 和 await 中真正起作用的是 await。async 关键字,无论从哪方面看,都不过是一个标识符。毕竟异步函数如果不包含 await 关键字,其执行基本上跟普通函数没有区别。

await

通常使用 await 是后面会跟上一个表达式,这个表达式会返回一个 Promise。await 它可以放在任何异步的,基于 Promise 的函数之前。它会暂停代码在该行上,等到后面的 Promise 完成,然后返回结果值。不出结果之前,后面的代码不会执行。
注意 await 关键字只能放到 async 函数里面,现在写一个函数,让它返回 Promise 对象。该函数的作用是 2s 后让数值乘以 2。

function doubleAfter2Seconds(num) {
  return new Promise((resolve,reject) => {
    setTimeout(()=> {
      resolve(2 * num)
    }, 2000)
  })
}

现在再写一个 async 函数,从而可以使用 await 关键字。await 后面放置的就是返回 Promise 对象的一个表达式,所以它后面可以写上 doubleAfter2Seconds 函数的调用。

async function testResult() {
  let result = await doubleAfter2Seconds(30)
  console.log(result)
}

// 现在调用testResult函数
testResult()

// 60 (2s之后)

现在我们看看代码的执行过程,调用 testResult 函数,它里面遇到了 await ,await 表示等一下。代码就暂停在这里,不再往下执行了,它在等什么?
等后面的 Promise 对象执行完毕,然后拿到 Promise resolve 的值并进行返回。返回值拿到后,它继续往下执行。遇到 await 代码暂停执行,等待 doubleAfter2Seconds 执行完毕。doubleAfter2Seconds 返回的 Promise 开始执行,2秒后 Promise resolve,并返回了值为60,这时 await 才拿到返回值60.然后赋值给 result,暂停结束,代码继续执行 console.log() 语句。
就这一个函数,我们可能看不出 async 和 await 的作用,如果我们要计算3个数的值,然后把得到的值进行输出呢?

async function testResult() {
  let first = await doubleAfter2seconds(30)
  let second = await doubleAfter2seconds(50)
  let third = await doubleAfter2seconds(30)
  console.log(first + second + third)
}

原文链接

异步代码处理方式

暨 async 和 await 是如何实现的? 需求:连续发送三个请求,下一个请求将上一个请求的结果作为下一次请求的参数。
发起请求的代码:

function requestData(url) {
  return new Promise((resolve, reject) {
    // 模拟网络请求
    setTimeout(function(){
      resolve(url)
    }, 2000)
  })
}

方案一:请求处理函数中再发起请求

requestData('lxx').then(res => {
  requestData(res + 'aaa').then(res => {
    requestData(res + 'bbb').then(res => {
      console.log(res) // lxxaaabbb
    })
  })
})

方案二:返回 Promise

requestData('lxx').then(res => {
  return requestData(res + 'aaa')
}).then(res => {
  return requestData(res + 'bbb')
}).then(res => {
  console.log(res)
})

方案三:Promise + Generator g.next().value 结果是一个 Promise,因为 requestData 返回一个 Promise then 方法中拿到 Promise 的结果 res,传入到下一个 next() 方法中。next() 方法的参数作为上一个 yield 的返回值如:res1,res1 即上一次 Promise 的结果。拿到上一个 yield 的返回值即上一次 Promise 的结果,就可以再次执行 yield

// 生成器函数
function *getData() {
  const res1 = yield requestData('lxx')
  const res2 = yield requestData(res1 + 'aaa')
  const res3 = yield requestData(res2 + 'bbb')
  console.log(res3) // lxxaaabbb
}
// g 是生成器对象
const g = getData()

// 1. 手动执行生成器函数
g.next().value.then(res => {
  g.next().value.then(res => {
    g.next().value.then(res => {
      console.log(res)
    })
  })
})

// 2. 生成器函数执行自动化
function execGeneratorFn(genFn) {
  const generator = getFn()
  function exec(res) {
    const result = generator.next(res)
    if(result.done) {
      return result.value
    }
    result.value.then(res => {
      exec(res)
    })
  }
  exec()
}
// 自动执行生成器函数
execGeneratorFn(getData)

上面生成器函数自动化函数在 npm 里有 co 包,在 async 和 await 标准化之前,我们一般使用 co 来处理生成器函数的自动化执行。

// 安装co
npm i co
// 自动执行生成器函数
const co = require('co')
co(getData)

方案四:async 和 await

async function getData(url) {
  let res1 = await requestData(url)
  let res2 = await requestData(res1 + 'aaa')
  let res3 = await requestData(res2 + 'bbb')
  console.log(res3)
}
getData('lxx') // lxxaaabbb

async 函数返回值

异步函数始终返回期约对象。当这个 Promise 有了结果(resolve或reject)会进入对应的 .then.catch 方法。.then.catch 方法是异步的,会被加入到微任务队列。

返回普通值

如果 async 函数中有返回一个值,当调用该函数时,内部会调用 Promise.resolve() 方法把它转化成一个 Promise 对象作为返回值。

async function foo(flag) {
  if(flag) {
    return 'hello world'
  } else {
    throw 'my god, failure'
  }
}
foo(true) // 调用 Promise.resolve() 返回 promise 对象
foo(false) // 调用 Promise.reject() 返回 promise 对象

返回 thenable 对象

thenable 中 resolve 的值会作为 async 函数返回的 Promise 的 resolve 值,并进入 .then 回调;thenable 中 reject 的值会作为 async 函数返回的 Promise 的 reject 值,并进入 .catch 回调。

async function foo() {
  return {
    then:function(resolve, reject) {
      resolve('hello world')
      // 或
      reject('error')
    }
  }
}

const promise = foo()
promise.then(res => {
  console.log(res) // hello world
}).catch(err => {
  console.log(err) // error
})

返回 Promise

async 函数返回的 Promise 的 resolve 值进入 .then 回调,reject 值进入 .catch 回调。

async function foo() {
  return new Promise((resolve, reject)=> {
    setTimeout(()=> {
      resolve('hello world')
      // 或
      reject('error')
    }, 2000)
  })
}

const promise = foo()
promise.then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})

async 函数异常处理

异步函数中的异常,会作为异步函数返回的 Promise 的 reject 值,进入 .catch 回调。

async function foo() {
  console.log('foo function start')
  console.log('中间代码')
  
  throw new Error('error message')
  console.log('foo function end') // 不会执行
}

const promise = foo()
promise.catch(err => {
  console.log(err.message) // error message
})
console.log('后续代码')

如果函数内部抛出错误,Promise 对象有一个 catch 方法进行捕获,如果没有捕获,程序会退出。