Promise 是我们常用的一个对象,它是用来处理异步操作逻辑的。其中,它有三种状态(pending,fulfilled,rejected),用来表示当前的处理状态。
简单实现
我们自己实现一个Promise
应该怎么实习呢?
首先,我们先写出实现核心逻辑
- 异步
- 链式调用
这也是面试题中常见的手写Promise
function MyPromise(executor) {
const self = this
// 存储resolve后的回调函数
self.cbs = []
function resolve(value) {
// 异步处理
setTimeout(() => {
self.data = value
self.cbs.forEach((cb) => cb(value))
});
}
executor(resolve)
}
MyPromise.prototype.then = function (onResolved) {
// 返回一个新的MyPromise对象,链式调用
return new MyPromise((resolve) => {
this.cbs.push(() => {
// 执行onResolved回调,并将原MyPromise对象的data值作为参数传递
// this.data是上一个promise中保存的结果
const res = onResolved(this.data)
if (res instanceof MyPromise) {
// 如果是MyPromise对象,当它解决时,调用新MyPromise对象的resolve方法
res.then(resolve)
} else {
// 如果onResolved返回的不是MyPromise对象,直接用返回值解决新MyPromise对象
resolve(res)
}
})
})
}
// test case
new MyPromise((resolve) => {
setTimeout(() => {
resolve(1);
}, 500);
})
.then((res) => {
console.log(res);
return new MyPromise((resolve) => {
setTimeout(() => {
resolve(2);
}, 500);
});
})
.then(console.log);
上述的代码基本实现了核心的功能,现在有以下问题:
实现Promise A+
那么,如果我们来手写一个符合Promise A+
规范的Promise,应该怎么实现呢?
定义构造函数
function Promise(executor) {
var self = this
// 初始状态 pending
self.status = 'pending'
self.data = undefined
// 用来存resolve reject的回调函数
self.onResolvedCallback = []
self.onRejectedCallback = []
// 增加 resolve reject
function resolve() { }
function reject() { }
// 增加 try catch
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
Q1:我们能不能在外面定义resolve和reject呢?
- 可以,因为需要访问promise内部的属性,this的指向需要做绑定
function resolve(value){
}
function reject(reason){
}
function Promise(executor) {
try {
executor(resolve.bind(this), reject.bind(this))
} catch(e) {
reject.bind(this)(e)
}
}
resolve和reject函数
接下来实现resolve和reject函数
function MyPromise(executor) {
var self = this
self.status = 'pending'
self.data = undefined
self.onResolvedCallback = []
self.onRejectedCallback = []
// 增加 resolve reject
function resolve(value) {
if (self.status === 'pending') {
self.status = 'fulfilled'
self.data = value
for (let i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value)
}
}
}
function reject(reason) {
if (self.status === 'pending') {
self.status = 'rejected'
self.data = reason
for (let i = 0; i < self.onRejectedCallback.length; i++) {
self.onRejectedCallback[i](reason)
}
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then方法
then
方法是实现链式调用的核心机制,因为它被绑定在对象的原型上,使得可以顺畅地进行链式调用。
MyPromise.prototype.then = function (onResolved, onRejected) {
var self = this
var promise2
// 增加 onResolved onRejected
onResolved =
typeof onResolved === 'function'
? onResolved
: function (v) {
return v
}
onRejected =
typeof onRejected === 'function'
? onRejected
: function (r) {
throw r
}
if (self.status === 'fulfilled') {
return (promise2 = new MyPromise(function (resolve, reject) {
try {
var x = onResolved(self.data)
if (x instanceof MyPromise) {
x.then(resolve, reject)
}
resolve(x)
} catch (e) {
reject(e)
}
}))
}
if (self.status === 'rejected') {
return (promise2 = new MyPromise(function (resolve, reject) {
try {
var x = onRejected(self.data)
if (x instanceof MyPromise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
}))
}
if (self.status === 'pending') {
return (promise2 = new MyPromise(function (resolve, reject) {
self.onResolvedCallback.push(function () {
try {
var x = onResolved(self.data)
if (x instanceof MyPromise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
self.onRejectedCallback.push(function () {
try {
var x = onRejected(self.data)
if (x instanceof MyPromise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
}))
}
}
MyPromise.prototype.catch = function (onRejected) {
return this.then(null, onRejected)
}
then透传
虽然我们已经完成了核心功能的开发,但仍有几种特定场景未能覆盖。
new MyPromise(resolve => resolve(8))
.then()
.then()
.then(function foo(value) {
console.log(value);
})
//
new MyPromise(resolve=>resolve(8))
.then()
.catch()
.then(function(value) {
console.log(value);
})
因此,我们对then方法做了调整,现在它只需将接收到的值继续传递下去。
MyPromise.prototype.then = function (onResolved, onRejected) {
// ...省略
onResolved = typeof onResolved === 'function' ? onResolved : function (value) { return value }
onRejected = typeof onRejected === 'function' ? onRejected : function (reason) { return reason }
// ...省略
}}
不同Promise的交互
为了确保我们的Promise实现能够与其他Promise对象互操作,符合Promise/A+规范,我们的实现必须能够处理形如p.then的调用。
这就引出一个问题:
- 如何正确处理传递给then方法的参数?
根据规范,如果then方法接收到的参数不遵循预期标准,可能会引发几个问题:
- 回调函数可能会被同步执行,而不是按照规范要求的异步执行。
- 如果then返回了一个Promise,那么这个返回的Promise可能不会被正确处理。
- 可能导致回调函数被调用多次,违背了Promise规范中的一次性调用准则。
鉴于这些潜在的问题,我们必须设计一种机制来确保即使遇到非规范的then参数,我们的Promise实现也能够可靠地工作。
- 我们需要一种方法来检测传递给then的参数是否有效,并在必要时采取补救措施,以避免上述问题的发生。我们的目标是创建一个健壮的Promise实现,它能够在各种情况下表现一致,符合Promise/A+的要求。
// 同步执行❌
let badPromise = new MyPromise((resolve, reject) => {
resolve("This should not be synchronous");
});
badPromise.then(data => {
console.log(data); // 不符合规范的实现可能会同步打印这条消息
});
// 未正确处理返回的Promise❌
let promiseA = new MyPromise((resolve, reject) => {
resolve(20);
});
let promiseB = promiseA.then(value => {
return new MyPromise((resolve, reject) => {
resolve(value * 2);
});
});
promiseB.then(value => {
console.log(value); // 应该打印40,但是如果then的实现不符合规范,可能得不到预期结果
});
// 多次调用❌
let flawedPromise = new MyPromise((resolve, reject) => {
resolve("Should only be called once");
resolve("Called again?"); // 根据规范,这个调用应该被忽略
});
flawedPromise.then(data => {
console.log(data);
}, reason => {
console.log(reason);
});
这时候我们需要一个函数来处理这些特殊case
function resolvePromise(promise2, x, resolve, reject) {
let isCalled = false
let then
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise!'));
}
if (x instanceof MyPromise) {
if (x.status === 'pending') {
x.then(function (v) {
resolvePromise(promise2, v, resolve, reject)
}, reject)
} else if (x.status === 'fulfilled') {
resolve(x.data)
} else if (x.status === 'rejected') {
reject(x.data)
}
return
}
if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
// 2.3.3.1 因为x.then有可能是一个getter,这种情况下多次读取就有可能产生副作用
// 即要判断它的类型,又要调用它,这就是两次读取
// eg. Object.defineProperty(x, 'then', { get: function () { throw new Error() } })
then = x.then
if (typeof then === 'function') {
then.call(x, function rs(y) {
if (isCalled) return
isCalled = true
return resolvePromise(promise2, y, resolve, reject)
}, function rj(r) {
if (isCalled) return
isCalled = true
return reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (isCalled) return
isCalled = true
return reject(e)
}
} else {
resolve(x)
}
}
这时候,我们修改then
的逻辑处理
MyPromise.prototype.then = function (onResolved, onRejected) {
var self = this;
let promise2
// 确保 onResolved 和 onRejected 是函数
onResolved = typeof onResolved === 'function' ? onResolved : function (v) { return v; };
onRejected = typeof onRejected === 'function' ? onRejected : function (r) { throw r; };
return promise2 = new MyPromise(function (resolve, reject) {
if (self.status === 'fulfilled') {
setTimeout(() => {
try {
const x = onResolved(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
});
} else if (self.status === 'rejected') {
setTimeout(function () { // 异步执行onRejected
try {
const x = onRejected(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
} else if (self.status === 'pending') {
self.onResolvedCallback.push(function (value) {
try {
var x = onResolved(value)
resolvePromise(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
self.onRejectedCallback.push(function (reason) {
try {
var x = onRejected(reason)
resolvePromise(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
}
})
}
测试
我们怎么知道我们的代码是否符合Promise A+
的规范呢,这时候需要一个库来验证
首先保证运行的是comment js
在文件中定义一个deferred
方法,并导出Promise
对象
// 为了使用PromiseA+规范的测试代码而加入
Promise.deferred = Promise.defer = function () {
var dfd = {}
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
try {
module.exports = Promise
} catch (e) { }
npm i -g promises-aplus-tests
promises-aplus-tests Promise.js
停止一个Promise链
new Promise(function(resolve, reject) {
resolve(42)
})
.then(function(value) {
// "Big ERROR!!!"
})
.catch()
.then()
.then()
.catch()
.then()
如上述代码,当调用的过程中如果在中间的步骤出现Big Error
并且catch
到,但是后面有then,还是会继续执行。
- 那么,我们应该解决停止一个promise链呢?
根据上面实现的代码,我们透传是通过返回值,如果我们传入一个空函数,相当于截断了整个流程
Promise.cancel = Promise.stop = function() {
return new Promise(function(){})
}
- 修改上面的代码,当出错时截停
new Promise(function(resolve, reject) {
resolve(42)
})
.then(function(value) {
// "Big ERROR!!!"
return Promise.stop()
})
.catch()
.then()
.then()
.catch()
.then()
如果Promise
链上有错误,执行下面的代码,并没有将错误信息输出
new MyPromise(function (resolve) {
resolve(42)
}).then(function (value) {
alter(value)
})
为了使用者能够清楚的捕获到错误,我们应该捕获异常并抛出,此时修改一下reject
函数
function reject(reason) {
setTimeout(() => {
if (self.status === 'pending') {
self.status = 'rejected'
self.data = reason
// 如果没有对应的reject函数处理异常,抛出错误信息
if (self.onRejectedCallback.length === 0) {
console.error(reason)
}
for (let i = 0; i < self.onRejectedCallback.length; i++) {
self.onRejectedCallback[i](reason)
}
}
});
}
最后,加上done
方法,至此,我们已经通过了promise A+
的所有测试
MyPromise.prototype.done = function () {
return this.catch(function (e) { // 此处一定要确保这个函数不能再出错
console.error(e)
})
}
参考链接
原文链接:https://juejin.cn/post/7317149843037388819 作者:xianjianlf2