Async?Async!

135 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情 \

一、起源

JavaScript 编程中最困难的就是异步操作,所以一直有群技术大牛试图搬开这块巨石,以达到一劳永逸的效果。

向前追溯,从回调函数,到 Promise 对象,再到 Generator 函数,虽然能撬动这块巨石,但就是移不开或者说留

下一个大坑,让后人苦不堪言。由于它们抽象的底层机制和复杂性,都使得人们在这条路上寸步难行。

佛说:异步编程的最高境界,就是根本不用关心它是不是异步。

所以就算是异步I/O只是读取一个文件而已,但也要做到精益求精。

直到ES2017 标准引入了 async 函数,方才使异步操作变得更加方便。

\

竟然有人不相信光,async 函数就是宇宙尽头的亮光,救无数人于异步操作的水深火热之中。

二、我是谁?

我是async 函数,是 Generator 函数的语法糖,也就是他的简便方法。

用Generator 函数提取文件

var fs = require('fs');

var readFile = function (fileName){
  return new Promise(function (resolve, reject){
    fs.readFile(fileName, function(error, data){
      if (error) reject(error);
      resolve(data);
    });
  });
};

var gen = function* (){
  var f1 = yield readFile('/etc/fstab');
  var f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

化身为async 函数,就是下面这样。

var asyncReadFile = async function (){
  var f1 = await readFile('/etc/fstab');
  var f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

其实async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await。

三、async 函数的优点

async 函数对 Generator 函数的改进,体现在以下三点。

(1)内置执行器。  Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。使其步入普通函数的行列,一行执行

var result = asyncReadFile();

(2)更好的语义。  async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性。  co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

(4)返回值是promise。 这比Generator 返回函数是Iterator对象方便许多,直接可用then实现进一步操作,然后我们就会使用到then的语法糖await命令。

四、使用场景

原始的写法

使用promise方法

\

使用async和await方法

\

五、万一报错,那么...

注意:await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。

await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 报错
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

上面代码会报错,因为 await 用在普通函数之中了。但是,如果将 forEach 方法的参数改成 async 函数,也有问题。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 可能得到错误结果
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}

上面代码可能不会正常工作,原因是这时三个 db.post 操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用 for 循环。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await db.post(doc);
  }
}

如果确实希望多个请求并发执行,可以使用 Promise.all 方法。

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// 或者使用下面的写法

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}

- End -\

\