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() 方法接受一个数组作为参数,promise1、promise2、promise3 都是 Promise 实例,如果不是,就会先调用 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all() 方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
all的状态由Promise1、Promise2、Promise3决定,分成两种情况。
(1)只有 Promise1、Promise2、Promise3 的状态都变成 fulfilled,all 的状态才会变成 fulfilled,此时 Promise1、Promise2、Promise3 的返回值组成一个数组,传递给 all 的回调函数。
示例
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)只要 Promise1、Promise2、Promise3 之中有一个被 rejected,all 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 all 的回调函数。
示例
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 的回调函数。
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 链式调用这么写的例子:
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 来这么写:
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。
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。
russ