使用 async/await 优化异步编程

理解 JavaScript 的 async/await
ECMAScript 6 入门 - async 函数
ECMAScript 6 入门 - Generator 函数的异步应用

回调函数


使用回调函数的传统异步编程有可能嵌套多层回调函数,当嵌套真的多到一定程度时,视觉效果和维护难度都是地狱级的灾难,这也就是为什么会把多层回调嵌套叫做回调地狱。多个异步操作形成强耦合,看着不知所云,改起来头晕目眩,是人都要崩溃了。

fetch(urlA, function (data) {
  fetch(urlB, function (data) {
    // ... 不停地在回调函数中再写新的回调函数
  });
});

Promise


一、特点

将回调函数的嵌套改成链式调用写法

二、链式调用

new Promise((resolve) => {
    resolve(fetch(url));
}).then((data) => {
    return data;
}).then((data) => {
    // … 不停地继续链式调用
});

链式调用比回调嵌套可读性更高,比较符合阅读逻辑:从上到下,异步取到一个结果,再执行调用链的下一个操作。
但是,假如链中有一个函数需要被另一个异步操作复用呢,例如这样:

new Promise((resolve) => {
    resolve(fetch(url));
}).then((data) => {
    reuseCode(); // 需复用的代码
});

在一个 Promise 链中要插入一个几乎毫不相关的异步操作是很奇怪的,体会一下:

new Promise((resolve) => {
    resolve(fetch(urlA)); // 异步操作 A
}).then((data) => {
    return (data, fetch(urlB)); // 插入异步操作 B
}).then((dataA, dataB) => {
    dataA ? reuseCode(dataA) : reuseCode(dataB); // 需复用的代码
});

新加入的这一层调用链,进行了异步操作 B ,并将结果向下一级以供复用代码使用,除此之外,还要原封不动地把异步操作 A 的结果往下传,不仅代码比较繁杂,逻辑上也很奇怪。就好像好好的一个家,莫名其妙突然来了个流浪汉,除了白用我家的锅碗瓢盆之外并不能给家庭做出什么贡献,多么别扭。

这样一来,原家庭成员们不可避免地会有这样一个想法:这个流浪汉倒也不是多讨厌,给他点吃的还是可以的,只要他老老实实呆在家门外面,别来打搅我们的生活就好了。

这种情况放在代码里,就是用同步的写法来写异步,没有整体性过强的 then 调用链的存在,代码自然会灵活很多。要达到这个目的,可以用 async/await 来实现。

async/await


 

一、特点

async/await 最大的特点就是将异步逻辑用同步写法实现。使用 async/await 改造上文例子:

async doSomething {
    let dataA = await fetch(urlA)); // 异步操作 A
    let dataB = await fetch(urlB); // 异步操作 B
    dataA ? reuseCode(dataA) : reuseCode(dataB); // 需复用的代码
}
doSomething();

取消调用链的写法,改用同步写法,逻辑更清晰了。

二、语法

async

  1. async 函数返回一个 Promise 对象。
  2. async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。

await 用法

  1. await 必须在 async 函数中使用。
  2. await 后跟一个 Promise 对象,取到这个 Promise 状态改变后(then)返回的数据。
  3. 当函数执行的时候,一旦遇到 await 就会先等到异步操作完成,再接着执行函数体内后面的语句。

async 函数实际上是 Generator 函数的语法糖。

三、Generator

语法

  1. 返回一个遍历器对象
  2. 内部有 yield 断点,需要执行器使得其继续执行
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
var hw = helloWorldGenerator();

异步应用

function* gen(){
  var result = yield fetch(url); // 异步操作写在 Generator 中
}

var g = gen(); // 获取遍历器对象
var result = g.next(); // 使遍历器执行,获取 fetch 到的 Promise 对象

result.value.then(function(data){ // 取到 Promise 对象后还是用链式调用写法
  return data.json();
}).then(function(data){
  g.next(data);
}).then(function(data){
    ……
});

虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便,需要在链式调用中不停地用 next 使遍历器执行。

与 async 的比较

ECMAScript 6 入门 - async 函数
async 函数对 Generator 函数的改进,体现在以下四点。

  1. async 函数内置执行器,会自动执行,输出最后结果。
    这完全不像 Generator 函数,需要调用 next 方法,或者用 co 模块,才能真正执行,得到最后结果。
  2. 更好的语义。async和await,比起星号和yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
  3. 更广的适用性。
    co 模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
  4. 返回值是 Promise。
    async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

你可能感兴趣的:(前端)