一、 Promise处理异步
1.1 基本用法
1 2 3 4 5 6 7 8 9
| const promise = new Promise(function(resolve, reject) { setTimeout(() => { if (/*成功*/){ resolve(value); } else { reject(error); } }) });
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const p = new Promise((resolve, reject) => { console.log(111); resolve(444) console.log(222); }); p.then((value) => { console.log(value); }); console.log(333)
// 输出顺序 111 222 333 444
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function loadImageAsync (url) { return new Promise(function (resolve, reject) { const image = new Image() // 加载成功后resolve image.onload = function() { resolve(image) } image.onerror = function() { reject(new Error('Could not load image at ' + url)) }
image.src = url }) }
|
1.2 执行情况
1 2 3 4 5 6 7 8 9 10 11
| const p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000) })
const p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000) })
p2 .then(result => console.log(result)) .catch(error => console.log(error))
|
1.2.2 Promise 在resolve后,再抛出错误,不会被捕获,因为 Promise 的状态一旦改变,就永久保持该状态
- resolve或reject后面的代码依然可以执行
1 2 3 4 5 6 7 8
| const promise = new Promise(function(resolve, reject) { resolve('ok'); console.log('ffff') throw new Error('test'); }); promise .then(function(value) { console.log(value) }) .catch(function(error) { console.log(error) });
|
1.2.3 then的参数不是函数,后面链式调用依然可以正常执行
1 2 3 4 5 6 7 8 9 10 11 12
| const p1 = new Promise(function (resolve, reject) { resolve(1); });
p1.then(3) .then((result) => { console.log(result, "result"); }) .catch((error) => { console.log(error, "error"); });
|
1.2.4 当 Promise 更新后,后面在调用依然可以拿到最新的值
1 2 3 4 5 6 7 8 9 10 11 12 13
| var promise1 = new Promise(function (resolve, reject) { resolve("promise 的resolve"); });
promise1.then((res) => { console.log(res, "1"); }); setTimeout(() => { promise1.then((res) => { console.log(res, "2"); }); }, 500);
|
1.3 Promise.all()
PromisePromise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组
只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值
const p = Promise.all([p1, p2, p3])
1.4 有时需要将现有对象转为 Promise 对象
1 2 3 4 5
| Promise.resolve() Promise.resolve('foo')
// 等价于 new Promise(resolve => resolve('foo'))
|
1.5 错误处理
普通情况的异步错误捕获
1 2 3 4 5 6 7 8 9 10 11 12
| const task = () => { setTimeout(() => { throw new Error('error') }) } function main() { try { task(); } catch(e) { console.log(e) } }
|
- then有两个参数,第一个是成功的回调,第二个是错误的回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| // 错误处理推荐 const promise = new Promise(function(resolve, reject) { throw new Error('test'); }); promise.catch(function(error) { console.log(error); });
// 不推荐这种 const promise = new Promise(function(resolve, reject) { try { throw new Error('test'); } catch(e) { reject(e); } }); promise.catch(function(error) { console.log(error); });
// 也不推荐第二个参数,在第一参数内报错不会被第二个参数捕获到 promise.then(function (res) {}, function (err) {})
|
1 2 3 4 5 6 7 8 9 10
| function main() { try { new Promise(() => { throw new Error('promise1 error') }) } catch(e) { // 无法捕获错误 console.log(e.message); } }
|
1 2 3 4 5 6 7 8 9 10
| const p3 = () => new Promise((reslove, reject) => { setTimeout(() => { reject('async error'); }, 1000) });
function main() { p3().catch(e => console.log(e)); } main();
|
1.6 finally()
1 2 3 4 5 6
| Promise.prototype.finally() finally方法不管 Promise 对象最后状态如何,都会执行
promise .then(result => {···}) .catch(error => {···}) .finally(() => {···})
|
1.7 Promise 链式调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| var promise1 = new Promise(function (resolve, reject) { resolve('第一个promise 的resolve') })
promise1.then(res => { // promise then的第一参数是成功时执行,第二个是失败时执行 console.log(res, '1') }) .then(res => { // 多个then连续调用时,只有第一个then能拿到resolve的值,后续的只是会被执行但是无法获取resolve的值。 console.log(res, '2') }) .then(res => { console.log(res, '3') // then内返回一个Promise对象时,后续的then都是依赖于返回的Promise 对象 return new Promise(function (resolve, reject) { setTimeout(_ => { resolve('第二个promise 的resolve') }, 3000) // b(new Error('zzzz')) }) }) .then(res => { // 该then取到的事上面返回的Promise的resolve的值。 console.log(res, '4') }) .then(res => { console.log(res, '5') }) .catch(res => { console.log(res, 'error') })
|
1.8 Promise 和 setTimeout (宏任务和微任务)
一个线程中,事件循环是唯一的,但是任务队列可以拥有多个
任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs
macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering
micro-task大概包括: process.nextTick, Promise, MutationObserver(html5新特性)
执行栈内执行上下文的同步任务按序执行,执行完即退栈,而当异步任务执行时,该异步任务进入等待状态(不入栈),同时通知线程:当触发该事件时(或该异步操作响应返回时),需向消息队列插入一个事件消息
当事件触发或响应返回时,线程向消息队列插入该事件消息(包含事件及回调)
当栈内同步任务执行完毕后,线程从消息队列取出一个事件消息,其对应异步任务(函数)入栈,执行回调函数,如果未绑定回调,这个消息会被丢弃,执行完任务后退栈
当线程空闲(即执行栈清空)时继续拉取消息队列下一轮消息,首先去拉取micro-task(微任务),直到所有执行完毕,然后循环再次从macro-task(宏任务)开始,这样一直循环下去
主线程不断重复上面的第4步

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| setTimeout(function() { console.log('timeout1'); new Promise(function(resolve) { resolve(); }).then(function() { console.log('timeout1_then') }) }) new Promise((a) => { a('ffff') }).then(_ => { console.log("promise outer") }) // 输出顺序 promise outer timeout1 timeout1_then
|
Promise 源码实现
分析Promise
从上面的基本使用,我们对 Promise 有大概的了解
Promise的构造方法接收一个executor(),在new Promise()时就立刻执行这个executor回调
resolve 成功执行then,reject 失败 执行catch
拥有 then, catch, resolve, all, finally
等方法
Promise 变为成功或失败后,状态无法在更改 resolve 后在 rejected 不会被捕获(Promise A+ 规定必须有三个状态 pending,fulfilled,rejected)Promise A+
Promise 可以链式调用,而且then, catch, finally 等都是异步执行
then(onFulfilled, onRejected) then 接收两个可选参数,一个成功回调,一个失败回调
Promise本质是一个状态机,且状态只能为以下三种:Pending(等待态)、Fulfilled(执行态)、Rejected(拒绝态),状态的变更是单向的,只能从Pending -> Fulfilled 或 Pending -> Rejected,状态变更不可逆
我们可以直接调用 Promise.all, Promise.reject, Promise.resolve
但是不能 new 之后调用,说明这三个方法是静态方法,不会被实例继承
从上面我们大概构造出一个轮廓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class CPromise { #status; constructor(executeFn) { this.#status = "pending"; const resolve = () => {}; const reject = () => {}; executeFn(resolve, reject); }
// then 方法 then(resFn, rejFn) {}
// catch 方法 catch(rejectFn) {}
// finally 方法 finally(callback) {}
// 静态的resolve 方法 static resolve(value) {}
// 静态的reject 方法 static reject(reason) {}
// 静态all 方法 static all(promiseArr) {} }
const p = new CPromise((res, rej) => {});
|
实现then
看一段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var promise1 = new Promise(function (resolve, reject) { resolve("promise 的resolve"); });
promise1 .then((res) => { console.log(res, "1"); }) .then((res) => { console.log(res, "3"); return 4 }) .then(res => { console.log(res, "4") }) promise1.then((res) => { console.log(res, "2"); });
// 输出 promise 的resolve 1 promise 的resolve 2 undefined "3" 4 "4"
|
根据上面分析下面四点
异步或者同步调用的 resolve 其值要传递给then的回调第一个参数, reject 值要传递个catch的回调第一个参数
无论调用多少个非链式调用的 then 或 catch 都能正确的取到 resolve 或 reject 传递的值
链式调用时,第二次及以后then无法得到 resolve 的值
then 和 catch 本身接收的都是函数参数
then可以链式调用并能够拿到上一个then的返回值,说明then返回的还是Promise对象,并且值能正确传递过来
分析上面四个特点,我们可以得知当 resolve 和 reject 时,通知 then 或 catch 接收的参数(是一个函数)调用并更新参数。而这种实现就是常听到的观察者模式。
观察者模式
定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使它们能够自动更新自己
使用场景: 当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,应考虑观察者模式
经过上面的分析,我们大概写出如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| class CPromise { #resolveQueue; #rejectQueue; constructor(executeFn) { // 接收订阅的数组,其实是每个then方法接收的函数入参数 this.#resolveQueue = []; this.#rejectQueue = []; // 当 resolve 或 reject 时,会触发所有的订阅更新 const resolve = (val) => { while (this.#resolveQueue.length) { const callback = this.#resolveQueue.shift(); callback(val); } }; const reject = (val) => { while (this.#rejectQueue.length) { const callback = this.#rejectQueue.shift(); callback(val); } }; executeFn(resolve, reject); }
then(resFn, rejFn) { return new CPromise((resolve, reject) => { const fulfilledFn = (value) => { try { // 获取then的返回值 let x = resFn(value); // 如果是Promise对象,执行一次新的then添加到订阅队列,如果不是,则直接解析 // 这段代码可以仔细思考一下,如果你then返回的Promise对象,后面链式调用的then其实你新实例化对象的then方法了。 // 如果then返回的是新的Promise对象A,这里首先 x instanceof CPromise 为 true, 并执行一次你返回Promise对象A的then收集到订阅队列 // 当你Promise对象A的resolve执行时,执行队列里的函数发布更新,而更新的函数就是当前then方法内自带的实例化Promise的resolve。 x instanceof CPromise ? x.then(resolve, reject) : resolve(x); } catch (error) { reject(error); } }; this.#resolveQueue.push(fulfilledFn);
const rejectedFn = (error) => { try { let x = rejFn(error); x instanceof CPromise ? x.then(resolve, reject) : reject(x); } catch (error) { reject(error); } }; this.#rejectQueue.push(rejectedFn); }); } }
// 使用我们自己的CPromise
const p = new CPromise((res, rej) => { setTimeout(() => { res(3); }, 1000); });
p.then((res) => { console.log(res, "位置1"); return 8; }) .then((res) => { console.log(res, "位置1-1"); return new CPromise((ress, rejj) => { // 注意这里,我加了异步,如果没有则拿不到7777的值 setTimeout(() => { ress(7777); }, 1000); }); }) .then((res) => { console.log(res, "位置1-2"); }); p.then((res) => { console.log(res, "位置2"); });
|
上面代码,初步来看结果已经符合 Promise 的使用表现了,不过还有两个问题
同步逻辑处理
观察上面代码的这段内容可以知道,如果我在new CPromise 时,同步执行 resolve 方法,此时还没有调用then方法,订阅队列里根本没有订阅内容,所以也不会有数据更新。
但是真正的Promise,不管你同步还是异步,它都能正确更新,而且无需我们resolve时加 setTimeout 处理。
因此,我们可以对 resolve 和 reject 方法进行处理,把更新订阅放到事件队列的最后面执行
1 2 3 4 5 6 7
| const resolve = (val) => { // 同步 resolve,会在then方法执行前出发,由于then还没有触发,订阅队列内并没有收集到订阅 while (this.#resolveQueue.length) { const callback = this.#resolveQueue.shift(); callback(val); } };
|
变成如下代码,reject 同理
1 2 3 4 5 6 7 8 9 10
| const resolve = (val) => { const exFn = () => { while (this.#resolveQueue.length) { const callback = this.#resolveQueue.shift(); callback(val); } }; // 这里这是用setTimeout添加到宏任务队列 setTimeout(exFn); };
|
状态处理
上面 1.2 执行情况中,当我 resolve 后,即使在 reject ,catch 也不会捕获到错误
当Promise的状态从 pending 变成 fulfilled 或 rejected 后,就不会在发生改变
另外即使 then的参数不是函数,链式调用依然不会受影响
当 Promise 更新后,后面在调用then依然可以拿到最新的值
为了方便测试比较,我们先把 catch 实现,catch和then的第二个参数是一样的,接收一个函数,函数的参数是捕获的错误,因此catch实现起来就比较简单了。
1 2 3
| catch(rejectFn) { return this.then(undefined, rejectFn) }
|
考虑上面then的非函数情况,和后续链式调用的正常使用,我们对 then 做进一步处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| class CPromise { #resolveQueue; #rejectQueue; constructor(executeFn) { this.#resolveQueue = []; this.#rejectQueue = []; const resolve = (val) => { const exFn = () => { while (this.#resolveQueue.length) { const callback = this.#resolveQueue.shift(); callback(val); } }; setTimeout(exFn); }; const reject = (val) => { const exFn = () => { while (this.#rejectQueue.length) { const callback = this.#rejectQueue.shift(); callback(val); } }; setTimeout(exFn); }; executeFn(resolve, reject); }
then(resFn, rejFn) { // 不是函数,容错处理,并保证链式调用的正常 if (typeof resFn !== "function") { resFn = (value) => value; } if (typeof rejFn !== "function") { rejFn = (errors) => errors; } return new CPromise((resolve, reject) => { const fulfilledFn = (value) => { try { let x = resFn(value); x instanceof CPromise ? x.then(resolve, reject) : resolve(x); } catch (error) { reject(error); } }; const rejectedFn = (error) => { try { let x = rejFn(error); x instanceof CPromise ? x.then(resolve, reject) : reject(x); } catch (error) { reject(error); } }; this.#resolveQueue.push(fulfilledFn); this.#rejectQueue.push(rejectedFn); }); }
// catch 实现 catch(rejectFn) { return this.then(undefined, rejectFn); } }
|
用我们实现的 CPromise 去执行下面代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const p1 = new CPromise((resolve, reject) => { resolve(1); reject(8); });
p1.then(3) .then((result) => { console.log(result, "result"); }) .catch((error) => { console.log(error, "error"); });
// 输出 1 "result" 8 "error"
|
很明显我们实现了,then入参非函数也能保证链式调用正常使用,但是 resolve 之后,reject 结果依然被 catch 捕获了,很明显这是不符合 Promise 的特性的,为此我们还需要处理状态变更
pending -> fulfilled
pending -> rejected
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| class CPromise { #status; #resolveQueue; #rejectQueue; #value; constructor(executeFn) { this.#status = "pending"; this.#resolveQueue = []; this.#rejectQueue = []; this.#value = undefined; const resolve = (val) => { const exFn = () => { if (this.#status !== "pending") return; this.#status = "fulfilled"; while (this.#resolveQueue.length) { const callback = this.#resolveQueue.shift(); callback(val); } }; setTimeout(exFn); }; const reject = (val) => { const exFn = () => { if (this.#status !== "pending") return; this.#status = "rejected"; while (this.#rejectQueue.length) { const callback = this.#rejectQueue.shift(); callback(val); } }; setTimeout(exFn); }; executeFn(resolve, reject); }
then(resFn, rejFn) { if (typeof resFn !== "function") { resFn = (value) => value; } if (typeof rejFn !== "function") { rejFn = (errors) => errors; } return new CPromise((resolve, reject) => { const fulfilledFn = (value) => { try { let x = resFn(value); x instanceof CPromise ? x.then(resolve, reject) : resolve(x); } catch (error) { reject(error); } }; const rejectedFn = (error) => { try { let x = rejFn(error); x instanceof CPromise ? x.then(resolve, reject) : reject(x); } catch (error) { reject(error); } }; switch (this.#status) { case "pending": this.#resolveQueue.push(fulfilledFn); this.#rejectQueue.push(rejectedFn); break; case "fulfilled": fulfilledFn(this.#value); break; case "rejected": rejectedFn(this.#value); break; default: break; } }); }
//catch catch(rejectFn) { return this.then(undefined, rejectFn); } }
|
到这里我们常用的 Promise 已经完成了。then 和 catch 以及一些表现形式都符合一个真正的Promise。接下来完善其他方法。
Promise.resolve
1 2 3 4
| static resolve(value) { if (value instanceof CPromise) return value; return new CPromise((resolve) => resolve(value)); }
|
Promise.reject
1 2 3
| static reject(reason) { return new CPromise((resolve, reject) => reject(reason)); }
|
Promise.finally
finally()方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。在finally之后,还可以继续then。并且会将值原封不动的传递给后面的then
1 2 3 4 5 6 7 8 9
| finally(callback) { return this.then( (value) => CPromise.resolve(callback()).then(() => value), (reason) => CPromise.resolve(callback()).then(() => { throw reason; }) ); }
|
Promise.all
Promise.all 方法返回一个 Promise 实例
所有的 promise 都 resolve 或参数中不包含 promise 时回调完成
如果有一个 rejected,此实例回调失败,失败原因的是第一个失败 promise 的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| static all(pro) { let index = 0; let result = []; return new CPromise((resolve, reject) => { pro.forEach((p, i) => { CPromise.resolve(p).then( (val) => { index++; result[i] = val; if (index === pro.length) { resolve(result); } }, (err) => { reject(err); } ); }); }); }
|
完整代码