从0到1实现Promise
前言
Promise大家一定都不陌生了,JavaScript异步流程从最初的Callback,到Promise,到Generator,再到目前使用最多的Async/Await(如果对于这些不熟悉的可以参考我另一篇文章《JavaScript异步编程》),这不仅仅是技术实现的发展,更是思想上对于如何控制异步的递进。Promise作为后续方案的基础,是重中之重,也是面试时候最常被问到的。
今天我们就一起从0到1实现一个基于A+规范的Promise,过程中也会对Promise的异常处理,以及是否可手动终止做一些讨论,最后会对我们实现的Promise做单元测试。完整的代码已经上传到github,想直接看代码的可以点这里。
虽然已经有很多带你实现Promise类的文章了,但每个人理解的程度不一样,也许不同的文章可以带给你不同的思考呢,那我们就开始吧。
正文
1. 基础框架
new Promise()时接收一个executor函数作为参数,该函数会立即执行,函数中有两个参数,它们也是函数,分别是resolve和reject,函数同步执行一定要放在try...catch中,否则无法进行错误捕获。
MyPromise.js
function MyPromise(executor) { function resolve(value) { } function reject(reason) { } try { executor(resolve, reject); } catch (reason) { reject(reason); } } module.exports = MyPromise;
resolve()接收Promise成功值value,reject接收Promise失败原因reason。
test.js
let MyPromise = require('./MyPromise.js'); let promise = new MyPromise(function(resolve, reject) { resolve(123); })
2. 添加状态机
目前实现存在的问题:
- Promise是一个状态机的机制,初始状态为
pending
,成功状态为fulfilled
,失败状态为rejected
。只能从pending
->fulfilled
,或者从pending
->rejected
,并且状态一旦转变,就永远不会再变了。
所以,我们需要为Promise添加一个状态流转的机制。
MyPromise.js
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; function MyPromise(executor) { let self = this; self.state = PENDING; function resolve(value) { if (self.state === PENDING) { self.state = FULFILLED; } } function reject(reason) { if (self.state === PENDING) { self.state = REJECTED; } } try { executor(resolve, reject); } catch (reason) { reject(reason); } } module.exports = MyPromise;
test.js
let MyPromise = require('./MyPromise.js'); let promise = new MyPromise(function(resolve, reject) { resolve(123); }); promise.then(function(value) { console.log('value', value); }, function(reason) { console.log('reason', reason); })
3. 添加then
方法
Promise拥有一个then
方法,接收两个函数 onFulfilled
和 onRejected
,分别作为Promise成功和失败的回调。所以,在then
方法中我们需要对状态state
进行判断,如果是fulfilled
,则执行onFulfilled(value)
方法,如果是rejected
,则执行onRejected(reason)
方法。
由于成功值value
和失败原因reason
是由用户在executor
中通过resolve(value)
和 reject(reason)
传入的,所以我们需要有一个全局的value
和reason
供后续方法获取。
MyPromise.js
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; function MyPromise(executor) { let self = this; self.state = PENDING; self.value = null; self.reason = null; function resolve(value) { if (self.state === PENDING) { self.state = FULFILLED; self.value = value; } } function reject(reason) { if (self.state === PENDING) { self.state = REJECTED; self.reason = reason; } } try { executor(resolve, reject); } catch (reason) { reject(reason); } } MyPromise.prototype.then = function(onFuifilled, onRejected) { let self = this; if (self.state === FULFILLED) { onFuifilled(self.value); } if (self.state === REJECTED) { onRejected(self.reason); } }; module.exports = MyPromise;
4. 实现异步调用resolve
目前实现存在的问题:
- 同步调用
resolve()
没有问题,但如果是异步调用,比如放到setTimeout
中,因为目前的代码在调用then()
方法时,state
仍是pending
状态,当timer到时候调用resolve()
把state
修改为fulfilled
状态,但是onFulfilled()
函数已经没有时机调用了。
针对上述问题,进行如下修改:
MyPromise.js
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; function MyPromise(executor) { let self = this; self.state = PENDING; self.value = null; self.reason = null; self.onFulfilledCallbacks = []; self.onRejectedCallbacks = []; function resolve(value) { if (self.state === PENDING) { self.state = FULFILLED; self.value = value; self.onFulfilledCallbacks.forEach(function(fulfilledCallback) { fulfilledCallback(); }); } } function reject(reason) { if (self.state === PENDING) { self.state = REJECTED; self.reason = reason; self.onRejectedCallbacks.forEach(function(rejectedCallback) { rejectedCallback(); }); } } try { executor(resolve, reject); } catch (reason) { reject(reason); } } MyPromise.prototype.then = function(onFuifilled, onRejected) { let self = this; if (self.state === PENDING) { self.onFulfilledCallbacks.push(() => { onFuifilled(self.value); }); self.onRejectedCallbacks.push(() => { onRejected(self.reason); }); } if (self.state === FULFILLED) { onFuifilled(self.value); } if (self.state === REJECTED) { onRejected(self.reason); } }; module.exports = MyPromise;
我们添加了两个回调函数数组onFulfilledCallbacks
和onRejectedCallbacks
,用来存储then()
方法中传入的成功和失败回调。然后,当用户调用resolve()
或reject()
的时候,修改state
状态,并从相应的回调数组中依次取出回调函数执行。
同时,通过这种方式我们也实现了可以注册多个then()
函数,并且在成功或者失败时按照注册顺序依次执行。
test.js
let MyPromise = require('./MyPromise.js'); let promise = new MyPromise(function(resolve, reject) { setTimeout(function() { resolve(123); }, 1000); }); promise.then(function(value) { console.log('value1', value); }, function(reason) { console.log('reason1', reason); }); promise.then(function(value) { console.log('value2', value); }, function(reason) { console.log('reason2', reason); });
5. then返回的仍是Promise
读过PromiseA+规范的同学肯定知道,then()
方法返回的仍是一个Promise,并且返回Promise的resolve
的值是上一个Promise的onFulfilled()
函数或onRejected()
函数的返回值。如果在上一个Promise的then()
方法回调函数的执行过程中发生了错误,那么会将其捕获到,并作为返回的Promise的onRejected
函数的参数传入。比如:
let promise = new Promise((resolve, reject) => { resolve(123); }); promise.then((value) => { console.log('value1', value); return 456; }).then((value) => { console.log('value2', value); }); let promise = new Promise((resolve, reject) => { resolve(123); });
打印结果为:
value1 123
value2 456
let promise = new Promise((resolve, reject) => { resolve(123); }); promise.then((value) => { console.log('value1', value); a.b = 2; // 这里存在语法错误 return 456; }).then((value) => { console.log('value2', value); }, (reason) => { console.log('reason2', reason); });
打印结果为:
value1 123
reason2 ReferenceError: a is not defined
可以看到,then()
方法回调函数如果发生错误,会被捕获到,那么then()
返回的Promise会自动变为onRejected
,执行onRejected()
回调函数。
let promise = new Promise((resolve, reject) => { reject(123); }); promise.then((value) => { console.log('value1', value); return 456; }, (reason) => { console.log('reason1', reason); return 456; }).then((value) => { console.log('value2', value); }, (reason) => { console.log('reason2', reason); });
打印结果为:
reason1 123
value2 456
好啦,接下来我们就去实现then()
方法依然返回一个Promise。
MyPromise.js
MyPromise.prototype.then = function(onFuifilled, onRejected) { let self = this; let promise2 = null; promise2 = new MyPromise((resolve, reject) => { if (self.state === PENDING) { self.onFulfilledCallbacks.push(() => { try { let x = onFuifilled(self.value); self.resolvePromise(promise2, x, resolve, reject); } catch(reason) { reject(reason); } }); self.onRejectedCallbacks.push(() => { try { let x = onRejected(self.reason); self.resolvePromise(promise2, x, resolve, reject); } catch(reason) { reject(reason); } }); } if (self.state === FULFILLED) { try { let x = onFuifilled(self.value); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } } if (self.state === REJECTED) { try { let x = onRejected(self.reason); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } } }); return promise2; };
可以看到,我们新增了一个promise2
作为then()
方法的返回值。通过let x = onFuifilled(self.value)
或者 let x = onRejected(self.reason)
拿到then()
方法回调函数的返回值,然后调用self.resolvePromise(promise2, x, resolve, reject)
,将新增的promise2
、x
、promise2
的resolve
和reject
传入到resolvePromise()
中。
所以,下面我们重点看一下resolvePromise()
方法。
MyPromise.js
MyPromise.prototype.resolvePromise = function(promise2, x, resolve, reject) { let self = this; let called = false; // called 防止多次调用 if (promise2 === x) { return reject(new TypeError('循环引用')); } if (x !== null && (Object.prototype.toString.call(x) === '[object Object]' || Object.prototype.toString.call(x) === '[object Function]')) { // x是对象或者函数 try { let then = x.then; if (typeof then === 'function') { then.call(x, (y) => { // 别人的Promise的then方法可能设置了getter等,使用called防止多次调用then方法 if (called) return ; called = true; // 成功值y有可能还是promise或者是具有then方法等,再次resolvePromise,直到成功值为基本类型或者非thenable self.resolvePromise(promise2, y, resolve, reject); }, (reason) => { if (called) return ; called = true; reject(reason); }); } else { if (called) return ; called = true; resolve(x); } } catch (reason) { if (called) return ; called = true; reject(reason); } } else { // x是普通值,直接resolve resolve(x); } };
resolvePromise()
是用来解析then()
回调函数中返回的仍是一个Promise
,这个Promise
有可能是我们自己的,有可能是别的库实现的,也有可能是一个具有then()
方法的对象,所以这里靠resolvePromise()
来实现统一处理。
下面是翻译自PromiseA+规范关于resolvePromise()
的要求:
Promise 解决过程
Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。
这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。
运行 [[Resolve]](promise, x) 需遵循以下步骤:
- x 与 promise 相等
如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
- x 为 Promise
如果 x 为 Promise ,则使 promise 接受 x 的状态:
- 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝 - 如果 x 处于执行态,用相同的值执行 promise - 如果 x 处于拒绝态,用相同的据因拒绝 promise
- x 为对象或函数
如果 x 为对象或者函数:
- 把 x.then 赋值给 then - 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise - 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise: - 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y) - 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise - 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用 - 如果调用 then 方法抛出了异常 e: - 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之 - 否则以 e 为据因拒绝 promise - 如果 then 不是函数,以 x 为参数执行 promise - 如果 x 不为对象或者函数,以 x 为参数执行 promise
如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise。
参考上述规范,结合代码中的注释,相信大家可以理解resolvePromise()
的作用了。
测试:
test.js
let MyPromise = require('./MyPromise.js'); let promise = new MyPromise(function(resolve, reject) { setTimeout(function() { resolve(123); }, 1000); }); promise.then((value) => { console.log('value1', value); return new MyPromise((resolve, reject) => { resolve(456); }).then((value) => { return new MyPromise((resolve, reject) => { resolve(789); }) }); }, (reason) => { console.log('reason1', reason); }).then((value) => { console.log('value2', value); }, (reason) => { console.log('reason2', reason); });
打印结果:
value1 123
value2 789
6. 让then()
方法的回调函数总是异步调用
官方Promise
实现的回调函数总是异步调用的:
console.log('start'); let promise = new Promise((resolve, reject) => { console.log('step-'); resolve(123); }); promise.then((value) => { console.log('step--'); console.log('value', value); }); console.log('end');
打印结果:
start
step-
end
step--
value1 123
Promise属于微任务,这里我们为了方便用宏任务setTiemout
来代替实现异步,具体关于宏任务、微任务以及Event Loop可以参考我的另一篇文章带你彻底弄懂Event Loop。
MyPromise.js
MyPromise.prototype.then = function(onFuifilled, onRejected) { let self = this; let promise2 = null; promise2 = new MyPromise((resolve, reject) => { if (self.state === PENDING) { self.onFulfilledCallbacks.push(() => { setTimeout(() => { try { let x = onFuifilled(self.value); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }, 0); }); self.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(self.reason); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }, 0); }); } if (self.state === FULFILLED) { setTimeout(() => { try { let x = onFuifilled(self.value); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }, 0); } if (self.state === REJECTED) { setTimeout(() => { try { let x = onRejected(self.reason); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }, 0); } }); return promise2; };
测试:
test.js
let MyPromise = require('./MyPromise.js'); console.log('start'); let promise = new MyPromise((resolve, reject) => { console.log('step-'); setTimeout(() => { resolve(123); }, 1000); }); promise.then((value) => { console.log('step--'); console.log('value', value); }); console.log('end');
打印结果:
start
step-
end
step--
value1 123
经过以上步骤,一个最基本的Promise就已经实现完了,下面我们会实现一些不在PromiseA+规范的扩展方法。
7. 实现catch()
方法
then()
方法的onFulfilled
和onRejected
回调函数都不是必传项,如果不传,那么我们就无法接收reject(reason)
中的错误,这时我们可以通过链式调用catch()
方法用来接收错误。举例:
let promise = new Promise((resolve, reject) => { reject('has error'); }); promise.then((value) => { console.log('value', value); }).catch((reason) => { console.log('reason', reason); });
打印结果:
reason has error
不仅如此,catch()
可以作为Promise链式调用的最后一步,前面Promise发生的错误会冒泡到最后一个catch()
中,从而捕获异常。举例:
let promise = new Promise((resolve, reject) => { resolve(123); }); promise.then((value) => { console.log('value', value); return new Promise((resolve, reject) => { reject('has error1'); }); }).then((value) => { console.log('value', value); return new Promise((resolve, reject) => { reject('has error2'); }); }).catch((reason) => { console.log('reason', reason); });
打印结果:
value 123
reason has error1
那么catch()
方法到底是如何实现的呢?
答案就是在Promise的实现中,onFulfilled
和onRejected
函数是有默认值的:
MyPromise.js
MyPromise.prototype.then = function(onFuifilled, onRejected) { onFuifilled = typeof onFuifilled === 'function' ? onFuifilled : value => {return value;}; onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}; }; MyPromise.prototype.catch = function(onRejected) { return this.then(null, onRejected); };
可以看到,onRejected
的默认值是把错误reason
通过throw
抛出去。由于我们对于同步代码的执行都是在try...catch
中的,所以如果Promise发生了错误,如果没传onRejected
,默认的函数会把错误reason
抛出,然后会被promise2捕捉到,作为reject(reason)
决议。
catch()
实现就是调用this.then(null, onRejected)
,由于promise2
被reject
,所以会执行onRejected
回调,于是就捕捉到了第一个promise的错误。
总结来说,then()
方法中不传onRejected
回调,Promise
内部会默认帮你写一个函数作为回调,作用就是throw
抛出reject
或者try...catch
到的错误,然后错误reason
会被promise2
作为reject(reason)
进行决议,于是会被下一个then()
方法的onRejected
回调函数调用,而catch
只是写了一个特殊的then(null, onRejected)
而已。
所以,我们在写Promise
的链式调用的时候,在then()
中可以不传onRejected
回调,只需要在链式调用的最末尾加一个catch()
就可以了,这样在该链条中的Promise
发生的错误都会被最后的catch
捕获到。
举例1:
let promise = new Promise((resolve, reject) => { reject(123); }); promise.then((value) => { // 注意,不会走这里,因为第一个promise是被reject的 console.log('value1', value); return new Promise((resolve, reject) => { reject('has error1'); }); }).then((value) => { console.log('value2', value); return new Promise((resolve, reject) => { reject('has error2'); }); }, (reason) => { // 注意,这个then有onRejected回调 console.log('reason2', reason); }).catch((reason) => { // 错误在上一个then就被捕获了,所以不会走到这里 console.log('reason3', reason); });
打印结果:
reason2 123
举例2:
let promise = new Promise((resolve, reject) => { reject(123); }); promise.then((value) => { console.log('value1', value); return new Promise((resolve, reject) => { reject('has error1'); }); }).then((value) => { console.log('value2', value); return new Promise((resolve, reject) => { reject('has error2'); }); }).catch((reason) => { // 由于链条中的then都没有onRejected回调,所以会一直被冒泡到最后的catch这里 console.log('reason3', reason); });
catch
和then
一样都是返回一个新的Promise
。有的同学可能会有疑问,如果catch
中的回调执行也发生错误该怎么办呢,这个我们后续在Promise异常处理中再做讨论。
打印结果:
reason3 123
8. 实现finally
方法
finally
是某些库对Promise
实现的一个扩展方法,无论是resolve
还是reject
,都会走finally
方法。
MyPromise.js
MyPromise.prototype.finally = function(fn) { return this.then(value => { fn(); return value; }, reason => { fn(); throw reason; }); };
9. 实现done
方法
done
方法作为Promise
链式调用的最后一步,用来向全局抛出没有被Promise
内部捕获的错误,并且不再返回一个Promise
。一般用来结束一个Promise
链。
MyPromise.js
MyPromise.prototype.done = function() { this.catch(reason => { console.log('done', reason); throw reason; }); };
10. 实现Promise.all
方法
Promise.all()
接收一个包含多个Promise
的数组,当所有Promise
均为fulfilled
状态时,返回一个结果数组,数组中结果的顺序和传入的Promise
顺序一一对应。如果有一个Promise
为rejected
状态,则整个Promise.all
为rejected
。
MyPromise.js
MyPromise.all = function(promiseArr) { return new MyPromise((resolve, reject) => { let result = []; promiseArr.forEach((promise, index) => { promise.then((value) => { result[index] = value; if (result.length === promiseArr.length) { resolve(result); } }, reject); }); }); };
test.js
let MyPromise = require('./MyPromise.js'); let promise1 = new MyPromise((resolve, reject) => { console.log('aaaa'); setTimeout(() => { resolve(1111); console.log(1111); }, 1000); }); let promise2 = new MyPromise((resolve, reject) => { console.log('bbbb'); setTimeout(() => { reject(2222); console.log(2222); }, 2000); }); let promise3 = new MyPromise((resolve, reject) => { console.log('cccc'); setTimeout(() => { resolve(3333); console.log(3333); }, 3000); }); Promise.all([promise1, promise2, promise3]).then((value) => { console.log('all value', value); }, (reason) => { console.log('all reason', reason); })
打印结果:
aaaa
bbbb
cccc
1111
2222
all reason 2222
3333
11. 实现Promise.race
方法
Promise.race()
接收一个包含多个Promise
的数组,当有一个Promise
为fulfilled
状态时,整个大的Promise
为onFulfilled
,并执行onFulfilled
回调函数。如果有一个Promise
为rejected
状态,则整个Promise.race
为rejected
。
MyPromise.js
MyPromise.race = function(promiseArr) { return new MyPromise((resolve, reject) => { promiseArr.forEach(promise => { promise.then((value) => { resolve(value); }, reject); }); }); };
test.js
let MyPromise = require('./MyPromise.js'); let promise1 = new MyPromise((resolve, reject) => { console.log('aaaa'); setTimeout(() => { resolve(1111); console.log(1111); }, 1000); }); let promise2 = new MyPromise((resolve, reject) => { console.log('bbbb'); setTimeout(() => { reject(2222); console.log(2222); }, 2000); }); let promise3 = new MyPromise((resolve, reject) => { console.log('cccc'); setTimeout(() => { resolve(3333); console.log(3333); }, 3000); }); Promise.race([promise1, promise2, promise3]).then((value) => { console.log('all value', value); }, (reason) => { console.log('all reason', reason); })
打印结果:
aaaa
bbbb
cccc
1111
all reason 1111
2222
3333
12. 实现Promise.resolve
方法
Promise.resolve
用来生成一个fulfilled
完成态的Promise
,一般放在整个Promise
链的开头,用来开始一个Promise
链。
MyPromise.js
MyPromise.resolve = function(value) { let promise; promise = new MyPromise((resolve, reject) => { this.prototype.resolvePromise(promise, value, resolve, reject); }); return promise; };
test.js
let MyPromise = require('./MyPromise.js'); MyPromise.resolve(1111).then((value) => { console.log('value1', value); return new MyPromise((resolve, reject) => { resolve(2222); }) }).then((value) => { console.log('value2', value); })
打印结果:
value1 1111
value2 2222
由于传入的value
有可能是普通值,有可能是thenable
,也有可能是另一个Promise
,所以调用resolvePromise
进行解析。
12. 实现Promise.reject
方法
Promise.reject
用来生成一个rejected
失败态的Promise
。
MyPromise.js
MyPromise.reject = function(reason) { return new MyPromise((resolve, reject) => { reject(reason); }); };
test.js
let MyPromise = require('./MyPromise.js'); MyPromise.reject(1111).then((value) => { console.log('value1', value); return new MyPromise((resolve, reject) => { resolve(2222); }) }).then((value) => { console.log('value2', value); }).catch(reason => { console.log('reason', reason); });
打印结果:
reason 1111
13. 实现Promise.deferred
方法
Promise.deferred
可以用来延迟执行resolve
和reject
。
MyPromise.js
MyPromise.deferred = function() { let dfd = {}; dfd.promies = new MyPromise((resolve, reject) => { dfd.resolve = resolve; dfd.rfeject = reject; }); return dfd; };
这样,你就可以在外部通过调用dfd.resolve()
和dfd.reject()
来决议该Promise
。
13. 如何停止一个Promise
链
假设这样一个场景,我们有一个很长的Promise
链式调用,这些Promise
是依次依赖的关系,如果链条中的某个Promise
出错了,就不需要再向下执行了,默认情况下,我们是无法实现这个需求的,因为Promise
无论是then
还是catch
都会返回一个Promise
,都会继续向下执行then
或catch
。举例:
new Promise(function(resolve, reject) { resolve(1111) }).then(function(value) { // "ERROR!!!" }).catch() .then() .then() .catch() .then()
有没有办法让这个链式调用在ERROR!!!的后面就停掉,完全不去执行链式调用后面所有回调函数呢?
我们自己封装一个Promise.stop
方法。
MyPromise.js
MyPromise.stop = function() { return new Promise(function() {}); };
stop
中返回一个永远不执行resolve
或者reject
的Promise
,那么这个Promise
永远处于pending
状态,所以永远也不会向下执行then
或catch
了。这样我们就停止了一个Promise
链。
new MyPromise(function(resolve, reject) { resolve(1111) }).then(function(value) { // "ERROR!!!" MyPromise.stop(); }).catch() .then() .then() .catch() .then()
但是这样会有一个缺点,就是链式调用后面的所有回调函数都无法被垃圾回收器回收。
14. 如何解决Promise
链上返回的最后一个Promise
出现错误
看如下例子:
new Promise(function(resolve) { resolve(42) }).then(function(value) { a.b = 2; });
这里a不存在,所以给a.b赋值是一个语法错误,onFulfilled
回调函数是包在try...catch
中执行的,错误会被catch
到,但是由于后面没有then
或catch
了,这个错误无法被处理,就会被Promise
吃掉,没有任何异常,这就是常说的Promise有可能会吃掉错误。
那么我们怎么处理这种情况呢?
方法一
就是我们前面已经实现过的done()
。
new Promise(function(resolve) { resolve(42) }).then(function(value) { a.b = 2; }).done();
done()
方法相当于一个catch
,但是却不再返回Promise
了,注意done()
方法中不能出现语法错误,否则又无法捕获了。
方法二
普通错误监听window
的error
事件可以实现捕获
window.addEventListener('error', error => { console.log(error); // 不会触发 });
Promise没有被onRejected()
处理的错误需要监听unhandledrejection
事件
window.addEventListener('unhandledrejection', error => { console.log('unhandledrejection', error); // 可以触发,而且还可以直接拿到 promise 对象 });
14. 单元测试
结束
相关单元测试以及完整代码可以到我的github查看,如果对你有帮助的话,就来个star吧~