Promise 本身是一个状态机,每一个 Promise 实例只能有三个状态 pending、fulfilled(resolved)、rejected,并且状态之间的转换只能是 pending 到 fulfilled 和 pending 到 rejected,状态变化是不可逆的。
Promise 拥有一个 then 方法,这个方法可以被调用多次,并且返回一个 Promise 对象。
Promise 支持链式调用,内部保存有一个 value 值,用来保存上次执行的结果值;如果有报错,就说明保存的是异常信息。
Promise 的实现步骤
遵循 Promise/A+ 规范。
- 实现 Promise 的基本框架
- 增加状态机
- 实现 then 方法
- 实现异步调用
- 实现 then 的链式调用
- 实现 catch 的异常处理
1. 实现 Promise 的基本框架结构
- 使用
new的方式得到一个promise实例化对象,接收一个函数为参数-
该函数接收两个函数作为参数,
resolve和reject,代表着我们需要改变当前实例的状态到已完成或是已拒绝。 -
该函数在实例化过程中,会自执行,已供外部可以调用所提供的回调函数。
promise必须是一个对象或函数。并具有then方法。thenable(就是拥有.then()的对象)
-
- 依据
promise必须处于三种状态之一:等待中、已完成或已拒绝。- 状态的变更只能从
等待中过度到已完成或已拒绝 - 状态扭转后,是不可变的。
- 在已完成状态下,返回的
value值必须一个JavaScript的合法值(包含undefined、thenable或promise)。 - 在拒绝状态下,返回一个
reason值,代表拒绝原因。
- 状态的变更只能从
const PENDING = 'pending',
FULFILLED = 'fulfilled',
REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
// 提供给予外部回调函数(resolve、reject),来修改当前 promise 状态
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
}
}
// 执行,已供外部可以拿到回调函数
executor(resolve, reject);
}
then() {}
catch() {}
static resolve () {}
static reject() {}
static all () {}
static race() {}
}
module.exports = MyPromise;
大致依据这些要求,一个 Promise 大致的架子就出来。然后针对 Promises/A+ 规范的解决流程依依实现其功能。
2. 实现 then 方法
-
通过提供的
then方法,获取当前promise的值或原因。 -
then必须接收两个参数(可选):onFulfilled和onRejected- 两个参数必须是一个函数,如果不是就忽略。
-
onFulfilled是函数。onFulfilled必须在promise实例对象为「已完成」后调用它,以promise的值(value)作为此函数的参数。- 不能在
promise状态为「已完成」之前调用它。 - 不能被多次调用。
-
onRejected是函数。onRejected必须在promise实例对象为「已拒绝」后调用它,以promise的原因(reason)作为此函数的参数。- 不能在
promise状态为「已拒绝」之前调用它。 - 不能被多次调用。
-
onFulfilled和onRejected必须作为函数调用(没有this)。虽说没有
this但非要使用的话:- 在严格模式下,
this指向undefined。 - 非严格模式下,指向全局对象。
- 在严格模式下,
class MyPromise {
// 省略无关代码...
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw new Error(reason)
};
let isCalled = false; // 防止被重复调用
if (this.status === FULFILLED) {
if (isCalled) return
isCalled = true;
onFulfilled(this.value)
}
if (this.status === REJECTED) {
if (isCalled) return
isCalled = true;
onRejected(this.reason)
}
}
}
以上代码对于同步代码还行,与异步就不行了,如下:
const MyPromise = require("./src/MyPromise2");
const promise1 = new MyPromise((resolve, reject) => {
// resolve('success')
setTimeout(() => {
reject('fail')
}, 1000);
})
.then(
value => {
console.log("🚀 ~ file: index.js ~ line 38 ~ value", value)
},
reason => {
console.log("🚀 ~ file: index.js ~ line 39 ~ reason", reason)
}
)
异步执行之前 then 方法已经就执行完了,所以当时的 status 还处于在 等待中 ,并不知道后续状态已经变更。这里我们还得处理下:
- 当状态为
pending时,将接收的回调函数加入队列中 - 通过本身
promise提供给外部的回调函数,去触发队列收集中的回调函数
const PENDING = 'pending',
FULFILLED = 'fulfilled',
REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
// 针对异步回调,进行收集
this.onFulfilledList = [];
this.onRejectedList = []
// 提供给予外部回调函数(resolve、reject),来修改当前 promise 状态
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 发布
this.onFulfilledList.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 发布
this.onRejectedList.forEach(fn => fn())
}
}
// 执行,已供外部可以拿到回调函数
executor(resolve, reject);
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw new Error(reason)
};
let isCalled = false; // 防止被重复调用
if (this.status === FULFILLED) {
if (isCalled) return
isCalled = true;
onFulfilled(this.value)
}
if (this.status === REJECTED) {
if (isCalled) return
isCalled = true;
onRejected(this.reason)
}
if (this.status === PENDING) {
// 订阅
this.onFulfilledList.push(() => onFulfilled(this.value));
this.onRejectedList.push(() => onRejected(this.reason));
}
}
}
module.exports = MyPromise;
再来测试下之前的例子,对于异步地处理就正常了。
- 在执行上下文堆栈仅包含平台代码之前,不得调用
onFulfilled或onRejected。
这里的“平台代码”是指引擎,环境和
promise实现的代码。实际上,此要求可确保在调用事件循环之后,使用新堆栈异步执行onFulfilled和onRejected。可以使用 “宏任务”机制(如setTimeout或setImmediate)或 “微任务”机制(如MutationObserver或process.nextTick)来实现。由于promise实现被视为平台代码,因此它本身可能包含**任务调度队列 (task-scheduling queue)**或“trampoline”,在其中调用处理程序。
也就是说 onFulfilled 和 onRejected 函数的调用不能再当前执行上下堆栈上,通过上述说明,我们来利用 setTimeout 将 onFulfilled 和 onRejected 的执行塞入异步队列里。
class MyPromise {
// 省略无关代码...
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw new Error(reason)
};
let isCalled = false; // 防止被重复调用
if (this.status === FULFILLED) {
if (isCalled) return
isCalled = true;
setTimeout(() => {
onFulfilled(this.value)
}, 0)
}
if (this.status === REJECTED) {
if (isCalled) return
isCalled = true;
setTimeout(() => {
onRejected(this.reason)
}, 0)
}
if (this.status === PENDING) {
// 订阅
this.onFulfilledList.push(() => {
setTimeout(() => {
onFulfilled(this.value)
}, 0);
});
this.onRejectedList.push(() => {
setTimeout(() => {
onRejected(this.reason)
}, 0);
});
}
}
}
3. 实现 Promise 链式调用
-
then方法可能会在同一个promise中被多次使用,支持链式调用。- 当
promise为「已完成」状态,所有的onFulfilled回调函数必须按照then方法调用顺序执行。 - 当
promise为「已拒绝」状态,所有的onRejected回调函数必须按照then方法调用的顺序执行。
- 当
-
then必须返回一个promise,promise2 = promise1.then(onFulfilled, onRejected)- 如果
onFulfilled或onRejected返回的是一个值(x), 就执行[[Resolve]](promise2, x)([[Resolve]](promise2, x)调用携带promise2和x作为参数的函数) 这个函数会执行上一次返回来的promise。 - 如果
onFulfilled或onRejected抛出一个异常,promise的状态为 「已拒绝」,这个异常将为拒绝原因返回。 - 如果
onFulfilled不是一个函数,并且promise1为「已完成」状态,那么promise2的状态也为「已完成」并且以promise1的值作为返回。 - 如果
onRejeceted不是一个函数,并且promise1为 「已拒绝」状态,那么promise1的拒绝原因也作为promise2的拒绝原因。
- 如果
观看 Promises/A+ 规范, 在实现链式调用的时候,通过一个
[[Resolve]](promise2, x)的函数来实现完成的。
const PENDING = 'pending',
FULFILLED = 'fulfilled',
REJECTED = 'rejected';
/**
* promise 解析流程操作
* @param {Object} promise2 promise 实例化对象
* @param {*} x 值,传递过来回调函数的值 (普通的 JavaScript 值,thenable、promise、error)
* @param {*} resolve TODO: 因为目前 MyPromise 本身还没有实现 resolve 和 reject 的静态方法,所以暂时先传递进来
* @param {*} reject
*/
function resolutionProcedure (promise2, x, resolve, reject) {
}
class MyPromise {
// 省略无关代码...
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw new Error(reason)
};
let isCalled = false; // 防止被重复调用
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
if (isCalled) return
isCalled = true;
setTimeout(() => {
let x = onFulfilled(this.value)
resolutionProcedure(promise2, x, resolve, reject)
}, 0)
}
if (this.status === REJECTED) {
if (isCalled) return
isCalled = true;
setTimeout(() => {
let x = onRejected(this.reason)
resolutionProcedure(promise2, x, resolve, reject)
}, 0)
}
if (this.status === PENDING) {
// 订阅
this.onFulfilledList.push(() => {
setTimeout(() => {
let x = onFulfilled(this.value)
resolutionProcedure(promise2, x, resolve, reject)
}, 0);
});
this.onRejectedList.push(() => {
setTimeout(() => {
let x = onRejected(this.reason)
resolutionProcedure(promise2, x, resolve, reject)
}, 0);
});
}
})
return promise2;
}
}
定义好 resolutionProcedure 我们就来实现它:
-
如果
promise和x引用相同的对象,状态为「已拒绝」并返回TypeError作为原因拒绝promise。 -
如果
x是一个promise,就采用x的状态。- 如果
x是「等待中」状态,则promise保持为「等待中」,直到x处理完为「已完成」或 「已拒绝」 - 如果
x为「已完成」,则x的值作为promise的值。 - 如果
x为「已拒绝」,则x的原因作为promise的原因。
- 如果
-
如果
x是一个object或functionx存在then方法
首先存储对
x.then的引用,然后测试该引用,然后调用该引用的过程避免了对x.then属性的多次访问。此类预防措施对于确保访问者属性的一致性非常重要,因为访问者属性的值在两次检索之间可能会发生变化。-
如果在检索
x.then属性时抛出一个异常,状态扭转「已拒绝」并以这个异常作为原因拒绝promise。 -
如果
x.then()是一个函数,则以x作为其this指向,以resovlePromise为第一个参数,rejectPromise作为第二个参数传入。- 传递
y值调用resolvePromise,去执行[[Resolve]](promise, y) - 传递
r原因调用rejectPromise, 状态为 「已拒绝」并以r作为原因拒绝promise。 - 如果同时调用
resolvePromise和rejectPromise,或者对同一参数进行了多次调用,则第一个调用具有优先权,而任何其他调用将被忽略。 - 如果调用
x.then时抛出一个异常resolvePromise或rejectPromise已经被调用,则忽略它。- 否则以这个异常为原因拒绝
promise。
- 传递
-
如果
x.then不是一个函数,则以x为值实现promise。
-
如果
x即不是object也不是function,则以x为值实现promise。
function resolutionProcedure (promise2, x, resolve, reject) {
if(promise2 === x) {
// 通过 reject 抛出去时,被捕获时以是一个 String,不会立即提示,而需要下一个 then 时才能被抛出, 所以在这里直接进行抛出捕获
try {
throw new TypeError('TypeError: Chaining cycle detected for promise #<MyPromise>')
} catch (error) {
console.log(error)
reject(error)
}
}
let isCalled = false;
// 如果 x 是一个对象、函数、promise, 将执行 then 方法
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// x 是否存在 then 方法
try {
let then = x.then; // 先保存其引用
if (typeof then === 'function') {
if (isCalled) return
isCalled = true
then.call(x,
(y) => {
resolutionProcedure(promise2, y, resolve, reject)
},
(r) => {
reject(r)
}
)
} else {
// 不是一个函数
resolve(x)
}
} catch (error) {
reject(error)
}
} else {
// 普通值
resolve(x)
}
}
4. 实现 catch 的异常处理
将程序中的一些需要异常处理的地方加上 try...catch ,并实现 catch 实例化方法(特殊的 then 方法)。
const PENDING = 'pending',
FULFILLED = 'fulfilled',
REJECTED = 'rejected';
/**
* promise 解析流程操作
* @param {Object} promise2 promise 实例化对象
* @param {*} x 值,传递过来回调函数的值 (普通的 JavaScript 值,thenable、promise、error)
* @param {*} resolve TODO: 因为目前 MyPromise 本身还没有实现 resolve 和 reject 的静态方法,所以暂时先传递进来
* @param {*} reject
*/
function resolutionProcedure (promise2, x, resolve, reject) {
if(promise2 === x) {
// 通过 reject 抛出去时,被捕获时以是一个 String,不会立即提示,而需要下一个 then 时才能被抛出, 所以在这里直接进行抛出捕获
try {
throw new TypeError('TypeError: Chaining cycle detected for promise #<MyPromise>')
} catch (error) {
console.log(error)
reject(error)
}
}
let isCalled = false;
// 如果 x 是一个对象、函数、promise, 将执行 then 方法
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// x 是否存在 then 方法
try {
let then = x.then; // 先保存其引用
if (typeof then === 'function') {
if (isCalled) return
isCalled = true
then.call(x,
(y) => {
resolutionProcedure(promise2, y, resolve, reject)
},
(r) => {
reject(r)
}
)
} else {
// 不是一个函数
resolve(x)
}
} catch (error) {
reject(error)
}
} else {
// 普通值
resolve(x)
}
}
class MyPromise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
// 针对异步回调,进行收集
this.onFulfilledList = [];
this.onRejectedList = []
// 提供给予外部回调函数(resolve、reject),来修改当前 promise 状态
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 发布
this.onFulfilledList.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 发布
this.onRejectedList.forEach(fn => fn())
}
}
// 执行,已供外部可以拿到回调函数
try {
executor(resolve, reject);
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw new Error(reason)
};
let isCalled = false; // 防止被重复调用
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
if (isCalled) return
isCalled = true;
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.status === REJECTED) {
if (isCalled) return
isCalled = true;
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.status === PENDING) {
// 订阅
this.onFulfilledList.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0);
});
this.onRejectedList.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0);
});
}
})
return promise2;
}
// 特殊的 then, 只执行 onRejected 回调
catch() {
return this.then(undefined, onRejected);
}
static resolve () {}
static reject() {}
static all () {}
static race() {}
}
module.exports = MyPromise;