Skip to content
本页目录

JS 异步发展

异步进化史

  • 异步在实现上,依赖一些特殊的语法规则。从整体上来说,异步方案经历了如下的四个进化阶段:

  • 回调函数 —> Promise —> Generator —> async/await。

  • 其中 Promise、Generator 和 async/await 都是在 ES2015 之后,慢慢发展起来的、具有一定颠覆性的新异步方案。相较于 “回调函数 “时期的刀耕火种而言,具有划时代的意义。

回调地狱

  • 当回调只有一层的时候,看起来感觉没什么问题。但是一旦回调函数嵌套的层级变多了之后,代码的可读性和可维护性将会被破坏。

Promise 概念

Promise 对象有以下两个特点。

  • (1)对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

  • (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

promise.all()

Promise.all() 方法接受一个数组作为参数,promise1promise2promise3 都是 Promise 实例,如果不是,就会先调用 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all() 方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

  • all 的状态由 Promise1Promise2Promise3 决定,分成两种情况。

(1)只有 Promise1Promise2Promise3 的状态都变成 fulfilledall 的状态才会变成 fulfilled,此时 Promise1Promise2Promise3 的返回值组成一个数组,传递给 all 的回调函数。

示例

js
let Promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(console.log('promise1'));
  }, 1000);
});

let Promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(console.log('promise2'));
  }, 2000);
});

let Promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(console.log('promise3'));
  }, 3000);
});

let all = Promise.all([Promise1, Promise2, Promise3]).then(() => {
  console.log('promise.all');
});
//1s后 promise1
//2s后 promise2
//3s后 promise3  promise.all

(2)只要 Promise1Promise2Promise3 之中有一个被 rejectedall 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 all 的回调函数。

示例

js
let promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('promise1');
  }, 1000);
});

let promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('promise2-reject------------');
  }, 2000);
});

let promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('promise3');
  }, 3000);
});

Promise.all([promise1, promise2])
  .then((res) => {
    console.log('promise.all');
  })
  .catch((res) => {
    console.log(res);
  });
//2s 后输出 promise2-reject------------
//3s 后程序运行结束 reject后不会执行 Promise.all().then() 而是进入 .catch()方法中接受第一个reject结果

promise.race()

下面代码中,只要 promise1、promise2、promise3 之中有一个实例率先改变状态,race 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 race 的回调函数。

js
let promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('promise1');
  }, 2000);
});

let promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('promise2-reject------------');
  }, 1000);
});

let promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('promise3');
  }, 3000);
});

let race = Promise.race([promise2, promise3, promise1])
  .then((res) => {
    console.log(res);
  })
  .catch((res) => {
    console.log(res);
  });
//1s后输出 promise2-reject------------

Generator

  • Generator 一个有利于异步的特性是,它可以在执行中被中断、然后等待一段时间再被我们唤醒。通过这个“中断后唤醒”的机制,我们可以把 Generator 看作是异步任务的容器,利用 yield 关键字,实现对异步任务的等待。

比如咱们用 Promise 链式调用这么写的例子:

js
httpPromise(url1)
  .then((res) => {
    console.log(res);
    return httpPromise(url2);
  })
  .then((res) => {
    console.log(res);
    return httpPromise(url3);
  })
  .then((res) => {
    console.log(res);
    return httpPromise(url4);
  })
  .then((res) => console.log(res));

其实完全可以用 yield 来这么写:

js
function* httpGenerator() {
  let res1 = yield httpPromise(url1);
  console.log(res);
  let res2 = yield httpPromise(url2);
  console.log(res);
  let res3 = yield httpPromise(url3);
  console.log(res);
  let res4 = yield httpPromise(url4);
  console.log(res);
}
  • 这里要引入 TJ 大神写的 co 库,才能执行上面这个 Generator 函数。

async/await

  • 就当大家正在纷纷感慨 co 真好使,generator + promise + co 的异步方案真优雅时,更强的家伙出现了。他就是 async/await。
js
async function httpRequest() {
  let res1 = await httpPromise(url1);
  console.log(res1);
}
  • 这个 await 关键字的意思就是 “我要异步了,可能会花点时间,后面的语句都给我等着”。当我们给 httpPromise(url1) 这个异步任务应用了 await 关键字后,整个函数会像被 “yield”了一样,暂停下来,直到异步任务的结果返回后,它才会被“唤醒”,继续执行后面的语句。

  • 是不是觉得这个“暂停”、”唤醒“的操作,和 generator 异步非常相似?事实上,async/await 本身就是 generator 异步方案的语法糖。它的诞生主要就是为了这个单纯而美好的目的——让你写得更爽,让你写出来的代码更美。

特别注意

注:async/await 和 generator 方案,相较于 Promise 而言,有一个重要的优势: Promise 的错误需要通过回调函数捕获,try catch 是行不通的。而 async/await 和 generator 允许 try/catch

MIT Licensed