共计 18193 个字符,预计需要花费 46 分钟才能阅读完成。
前言
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);
});
打印结果:
reason has error 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.reace 方法
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.all([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 吧~
参考文档
PromiseA+ 规范