ECMAScript 6(23)async函数

1. async 简介

1.1 async 是什么?

  • 是Generator函数的语法糖,比Generator函数更强大一些。
  • 主要用于解决依次异步调用异步函数的问题。即当第一个异步调用结束后,再调用第二个异步函数;等第二个调用完成后再调用第三个的这种情况。以往实现需要进行回调函数多层嵌套才能实现,但这种写法会导致多层回调函数嵌套,形成可怕的回调地狱,既不方便维护,也不方便理解。而新的写法可以解决以上这些问题,让原本的回调地狱,变成类似同步函数一样的写法。极大的减轻了代码的复杂度。在async函数出来之前,解决这类问题如果不想用面对回调地狱,那么一般是借用Gererator函数,然后使用Thunk函数或者Co模块。二者实质上是类似的。

1.2 async示例

  • async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

例子 :

function timeout(ms,num){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve(num)
        },ms)
    })
}

async function asyncPrint(value, ms) {
  var a = await timeout(ms,1);
  console.log(a)
  var b = await timeout(ms,2);
  console.log(b)
  console.log(value)
  return value + 'world' // async 返回一个Promise对象,他的值是async函数中,return返回的值。
}

asyncPrint('hello',2000).then(res=>console.log(res))
// 1 2s后输出
// 2 4s后输出
// hello 4s后输出
// helle world 4s后输出
  • async函数内部return语句返回的值,会成为then方法回调函数的参数。
async function f() {
  return 'hello world';
}

f().then(v => console.log(v))

1.3 解释和说明

  1. async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await
  2. Generator 函数的执行必须靠执行器,async函数自带执行器。
  3. yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值。
  4. async函数的返回值是 Promise 对象
  5. async关键字是标明这个函数不是一般的函数,而是Generator函数(并且是有自动流程管理的)
  6. async和await是成套出现的,单纯只有一个是没有用的;
  7. await只能在async函数内,而不能在其他函数内
  8. await表达式的返回值,就是Promise对象传给resolve的值;
  9. 当第一个await表达式后面跟的Promise对象执行完毕之后(状态变成resolved)第二个才会继续执行,相当于同步函数。
  10. async函数的返回的是一个Promise对象,他的值是async函数中,return返回的值。
  11. async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
  12. asunc函数除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

1.4 async的几种写法

// 函数声明
async function foo() {}

// 函数表达式
const foo = async function () {};

// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)

// Class 的方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jake').then(…);

// 箭头函数
const foo = async () => {};

1.5 await

  • await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。await命令的参数是数值123,这时等同于return 123。
  • await命令后面是一个thenable对象(即定义了then方法的对象),那么await会将其等同于 Promise 对象
  • await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。
async function f() {
  await Promise.reject('出错了');
  // 或者
  // return Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
  • 任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}
  • 第一个await放在try…catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。
  • 另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。
async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}
// 或者
async function f() {
  await Promise.reject('出错了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world

2.错误处理

  • async函数内部的抛错,会使async函数返回的Promise对象的状态变为rejected,并使函数立即结束(不再执行抛错代码之后的代码)。
  • 假如async函数没有错误捕获机制,或者返回的Promise对象没有例如catch之类的错误处理机制,就会导致报错冒泡到外面来。
  • 虽然由于Promise对象本身是异步的原因,在全部代码执行完后错误才会被抛出来,不会影响其他代码的执行(一般不会)。但这显然是不好的。
async function Foo() {
    throw new Error("error")
    console.log('123')
}
Foo()
console.log("456")
// 456
// Uncaught (in promise) Error: error
  • 解决办法有两个,通过返回对象的catch来处理错误,或者是在async内通过try…catch来捕获错误。
  1. catch
async function f() {
  await Promise.reject('出错了');
}

f().catch(e => console.log(e))
// 出错了

或者

async function f() {
  await Promise.reject('出错了').catch(err=>console.log(err));
}

f()
// 出错了
  1. try…catch
async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
    console.log(e)
  }
  return await Promise.resolve('hello world');
}

f()
// 出错了

3.注意事项

  1. 多个await时, 只有前面的异步完成以后,才会执行后面的,这样比较耗时。
let foo = await getFoo();
let bar = await getBar();
  • 优化
  • 如果多个请求并发执行,可以使用Promise.all方法
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
// 这两种写法,getFoo和getBar都是同时触发,这样就会缩短程序的执行时间。
  1. await命令只能用在async函数之中,如果用在普通函数,就会报错。
function bar(callback) {
    callback()
}
async function Foo() {
    bar(function () {
        await "a"
    })
}
Foo()
// Uncaught SyntaxError: Unexpected string

4. async 函数的实现原理

  • async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
  • 阮一峰的实现方法
function spawn(genF) { // genF是一个 Generator 函数, 通过promise.then()返回callback实现自执行
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}
  • 参考文献 阮一峰es6

你可能感兴趣的:(ES6系列)