Promises A+ implementation with ES6 class
同步链接: https://www.shanejix.com/posts/Promises A+ implementation with ES6 class/
本文参考 Promises/A+
规范实现 new Promise()
,.then()
等功能,如不了解 Promise
请参考《Promise 必知必会》。更具体的 Promises/A+
规范见这里
demo
看一个测试代码test.js
let promise1 = new Promise(
(resolve, reject) => {
// write your code here
setTimeout(() => {
resolve("foo");
}, 300);
} /* executor */
);
promise1.then((value) => {
console.log(value); // expected output: "foo"
});
console.log(promise1); // expected output: [object Promise]
期望的test.js
执行阶段
new Promise() => setTimeout() => .then() => console.log(promise1)
输出日志如下
Promise {}
foo
但是这样不能明显的发现问题,修改test.js
如下
let promise1 = new Promise((resolve, reject) => {
resolve("foo");
});
promise1.then((value) => {
console.log(value);
});
console.log(promise1);
期望的test.js
执行阶段
new Promise() => resolve() => .then() => console.log(promise1)
输出日志如下
Promise {: 'foo'}
foo
这样问题就很明显了:我们期望的是在 执行rosolve()
之前就拿到 promise1.then()
中的回调函数 - 这就是Promise
实现异步操作的关键之处了
?
new Promise()
如何实现?
?
Promise
基本的使用
new Promise(
(resolve, reject) => {
// write your code here
} /* executor*/
);
干了什么
1. Promise构造函数执行时会调用 executor 函数
2. resolve 和 reject 两个内部函数作为参数传递给 executor
实现new Promise()
的构造函数框架如下(标准中没有指定构造函数的具体行为)
// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class Promise {
/**
* Promise 构造函数接收一个 executor 函数,
* executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
* @param {*} executor
*/
constructor(executor) {
// 2.1. Promise 的状态
// Promise 必须处于以下三种状态之一:pending,fulfilled 或者 rejected。
this.status = PENDING;
// 2.2.6.1. 如果 promise 处于 fulfilled 状态,所有相应的 onFulfilled 回调必须按照它们对应的 then 的原始调用顺序来执行
this.onFulfilledCallbacks = [];
// 2.2.6.2. 如果 promise 处于 rejected 状态,所有相应的 onRejected 回调必须按照它们对应的 then 的原始调用顺序来执行。
this.onRejectedCallbacks = [];
// 成功之后的值
this.value = null;
// 失败之后的原因
this.reason = null;
/**
* 更改成功后的状态
* @param {*} value
*/
const resolve = (value) => {
// todo
};
/**
* 更改失败后的状态
* @param {*} reason
*/
const reject = (reason) => {
// todo
};
// 传入的函数可能也会执行异常,所以这里用 try...catch 包裹
try {
// executor 是一个执行器,进入会立即执行,并传入resolve和reject方法
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
}
module.exports = Promise;
实现 resolve
和 reject
// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class Promise {
/**
* Promise 构造函数接收一个 executor 函数,
* executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
* @param {*} executor
*/
constructor(executor) {
// 2.1. Promise 的状态
// Promise 必须处于以下三种状态之一:pending,fulfilled 或者 rejected。
this.status = PENDING;
// 2.2.6.1. 如果 promise 处于 fulfilled 状态,所有相应的 onFulfilled 回调必须按照它们对应的 then 的原始调用顺序来执行
this.onFulfilledCallbacks = [];
// 2.2.6.2. 如果 promise 处于 rejected 状态,所有相应的 onRejected 回调必须按照它们对应的 then 的原始调用顺序来执行。
this.onRejectedCallbacks = [];
// 成功之后的值
this.value = null;
// 失败之后的原因
this.reason = null;
/**
* 更改成功后的状态
* @param {*} value
*/
const resolve = (value) => {
// 2.1.1. 当 Promise 处于 pending 状态时:
// 2.1.1.1. 可以转换到 fulfilled 或 rejected 状态。
// 2.1.2. 当 Promise 处于 fulfilled 状态时:
// 2.1.2.1. 不得过渡到任何其他状态。
// 2.1.2.2. 必须有一个不能改变的值。
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
// 2.2.6.1. 如果 promise 处于 fulfilled 状态,所有相应的 onFulfilled 回调必须按照它们对应的 then 的原始调用顺序来执行。
while (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(value);
}
}
};
/**
* 更改失败后的状态
* @param {*} reason
*/
const reject = (reason) => {
// 2.1.1. 当 Promise 处于 pending 状态时:
// 2.1.1.1. 可以转换到 fulfilled 或 rejected 状态。
// 2.1.3. 当 Promise 处于 rejected 状态时:
// 2.1.2.1. 不得过渡到任何其他状态。
// 2.1.2.2. 必须有一个不能改变的值。
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// 2.2.6.2. 如果 promise 处于 rejected 状态,所有相应的 onRejected 回调必须按照它们对应的 then 的原始调用顺序来执行。
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason);
}
}
};
// 传入的函数可能也会执行异常,所以这里用 try...catch 包裹
try {
// executor 是一个执行器,进入会立即执行,并传入resolve和reject方法
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
}
module.exports = Promise;
这里需要注意一个细节:多个 .then()
添加到同一个 promise
上
let promise = new Promise(function (resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function (result) {
alert(result); // 1
return result * 2;
});
promise.then(function (result) {
alert(result); // 1
return result * 2;
});
promise.then(function (result) {
alert(result); // 1
return result * 2;
});
这就是 **onFulfilledCallbacks**
和 **onRejectedCallbacks**
被处理得为数组得原因
?
.then()
根据 demo
中的启发:**promise.then()**
用来注册在这个 Promise 状态确定后的回调。需要注意的几点
?
- 很明显
.then()
方法需要写在原型链上 - 在
Promise/A+
标准中明确.then()
返回一个新对象(详情),Promise
实现中几乎都是返回一个新的**Promise**
对象
实现**then()**
**方法框架 **如下
// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class Promise {
/**
* Promise 构造函数接收一个 executor 函数,
* executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
* @param {*} executor
*/
constructor(executor) {
// ...
}
/**
* then方法接收两个参数,onResolved,onRejected,分别为Promise成功或失败后的回调
* @param {*} onResolved
* @param {*} onRejected
* @returns
*/
then(onResolved, onRejected) {
let promise2;
// 根据标准,如果then的参数不是function,则需要忽略它,此处以如下方式处理
onResolved = typeof onResolved === "function" ? onResolved : (v) => v;
onRejected =
typeof onRejected === "function"
? onRejected
: (r) => {
throw r;
};
if (this.status === "fulfilled") {
return (promise2 = new Promise((resolve, reject) => {
// todo
}));
}
if (this.status === "rejected") {
return (promise2 = new Promise((resolve, reject) => {
// todo
}));
}
if (this.status === "pending") {
return (promise2 = new Promise((resolve, reject) => {
// todo
}));
}
}
}
后续扩展发现,如下then
的结构更灵活:
// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class Promise {
/**
* Promise 构造函数接收一个 executor 函数,
* executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
* @param {*} executor
*/
constructor(executor) {
// ...
}
/**
* then方法接收两个参数,onResolved,onRejected,分别为Promise成功或失败后的回调
* @param {*} onResolved
* @param {*} onRejected
* @returns
*/
then(onResolved, onRejected) {
// 根据标准,如果then的参数不是function,则需要忽略它,此处以如下方式处理
onResolved = typeof onResolved === "function" ? onResolved : (v) => v;
onRejected =
typeof onRejected === "function"
? onRejected
: (r) => {
throw r;
};
const promise2 = new Promise((resolve, reject) => {
if (this.status === "fulfilled") {
// todo
}
if (this.status === "rejected") {
// todo
}
if (this.status === "pending") {
// todo
}
});
return promise2;
}
}
三种状态下的 Promise
都会返回 new Promise()
。返回的 promise2
的状态如何确定呢?
?
看个例子
const promise1 = new Promise((resovle, reject) => {
// ...
});
const promise2 = promise1.then(
(value) => {
return 4;
},
(reason) => {
throw new Error("sth went wrong");
}
);
根据标准,上述代码,promise2
的值取决于then
里面函数的返回值:
- 如果 promise1 被 resolve 了,promise2 的将被 4 resolve,
- 如果 promise1 被 reject 了,promise2 将被 new Error('sth went wrong') reject,
所以,需要在 then 内部执行 onResolved 或者 onRejected,并根据返回值(标准中记为 x)来确定 promise2 的结果。并且,如果 onResolved/onRejected 返回的是一个 Promise,promise2 将直接取这个 Promise 的结果.
?
具体实现
// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class Promise {
/**
* Promise 构造函数接收一个 executor 函数,
* executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
* @param {*} executor
*/
constructor(executor) {
// ...
}
/**
* 2.2. then 方法
* 一个 promise 必须提供一个 then 方法来访问其当前值或最终值或 rejected 的原因。
* 一个 promise 的 then 方法接受两个参数:
* promise.then(onFulfilled, onRejected)
* @param {*} onFulfilled
* @param {*} onRejected
* @returns
*/
then(onFulfilled, onRejected) {
// 2.2.1. onFulfilled 和 onRejected 都是可选参数:
// 2.2.1.1. 如果 onFulfilled 不是一个函数,它必须被忽略。
// 2.2.7.3. 如果 onFulfilled 不是一个函数且 promise1 为 fulfilled 状态,promise2 必须用和 promise1 一样的值来变为 fulfilled 状态。
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value;
// 2.2.1. onFulfilled 和 onRejected 都是可选参数:
// 2.2.1.2. 如果 onRejected 不是一个函数,它必须被忽略。
// 2.2.7.4. 如果 onRejected 不是一个函数且 promise1 为 rejected 状态,promise2 必须用和 promise1 一样的 reason 来变为 rejected 状态。
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason;
};
// 2.2.7. then 必须返回一个 promise
const promise2 = new Promise((resolve, reject) => {
const fulfilledMicrotask = () => {
// 2.2.4. onFulfilled 或 onRejected 在执行上下文堆栈仅包含平台代码之前不得调用。
// 3.1. 这可以通过“宏任务”机制(例如 setTimeout 或 setImmediate)或“微任务”机制(例如 MutationObserver 或 process.nextTick)来实现。
setTimeout(() => {
try {
// 2.2.2.1. onFulfilled 必须在 promise 的状态变为 fulfilled 后被调用,并将 promise 的值作为它的第一个参数。
// 2.2.5. onFulfilled 和 onRejected 必须作为函数调用。
const x = onFulfilled(this.value);
// 2.2.7.1. 如果 onFulfilled 或 onRejected 返回了一个值 x,则运行 Promise 处理程序 [[Resolve]](promise2, x)。
promiseResolutionHandler(promise2, x, resolve, reject);
} catch (error) {
// 2.2.7.2. 如果 onFulfilled 或 onRejected 抛出了一个异常,promise2 必须用 e 作为 reason 来变为 rejected 状态。
reject(error);
}
});
};
const rejectedMicrotask = () => {
// 2.2.4. onFulfilled 或 onRejected 在执行上下文堆栈仅包含平台代码之前不得调用。
// 3.1. 这可以通过“宏任务”机制(例如 setTimeout 或 setImmediate)或“微任务”机制(例如 MutationObserver 或 process.nextTick)来实现。
setTimeout(() => {
try {
// 2.2.3.1. 它必须在 promise 的状态变为 rejected 后被调用,并将 promise 的 reason 作为它的第一个参数。
// 2.2.5. onFulfilled 和 onRejected 必须作为函数调用。
const x = onRejected(this.reason);
// 2.2.7.1. 如果 onFulfilled 或 onRejected 返回了一个值 x,则运行 Promise 处理程序 [[Resolve]](promise2, x)。
promiseResolutionHandler(promise2, x, resolve, reject);
} catch (error) {
// 2.2.7.2. 如果 onFulfilled 或 onRejected 抛出了一个异常,promise2 必须用 e 作为 reason 来变为 rejected 状态。
reject(error);
}
});
};
// 2.2.2. 如果 onFulfilled 是一个函数:
// 2.2.2.1. 它必须在 promise 的状态变为 fulfilled 后被调用,并将 promise 的值作为它的第一个参数。
// 2.2.2.2. 它一定不能在 promise 的状态变为 fulfilled 前被调用。
// 2.2.2.3. 它最多只能被调用一次。
if (this.status === FULFILLED) {
// 如果promise1(此处即为this)的状态已经确定并且是fulfilled,调用 resolvedMicrotask
fulfilledMicrotask();
}
// 2.2.3. 如果 onRejected 是一个函数,
// 2.2.3.1. 它必须在 promise 的状态变为 rejected 后被调用,并将 promise 的 reason 作为它的第一个参数。
// 2.2.3.2. 它一定不能在 promise 的状态变为 rejected 前被调用。
// 2.2.3.3. 它最多只能被调用一次。
if (this.status === REJECTED) {
// 如果promise1(此处即为this)的状态已经确定并且是rejected,调用 rejectedMicrotask
rejectedMicrotask();
}
// 2.2.6. then 可能会被同一个 promise 多次调用。
if (this.status === PENDING) {
// 如果当前的Promise还处于pending状态,并不能确定调用onResolved还是onRejected,
// 只能等到Promise的状态确定后,才能确实如何处理
// 所以需要把**两种情况**的处理逻辑做为callback放入promise1(此处即this)的回调数组里
this.onFulfilledCallbacks.push(fulfilledMicrotask);
this.onRejectedCallbacks.push(rejectedMicrotask);
}
});
return promise2;
}
}
promiseResolutionHandler
集中处理程序
/**
* 2.3. Promise 处理程序
* Promise 处理程序是一个将 promise2 和 value 作为输入的抽象操作,将其表示为 [[Resolve]](promise2, x)。
* 补充说明:这里将 resolve 和 reject 也传入进来,因为后续要根据不同的逻辑对 promise2 执行 fulfill 或 reject 操作。
* @param {*} promise2
* @param {*} x
* @param {*} resolve
* @param {*} reject
* @returns
*/
function promiseResolutionHandler(promise2, x, resolve, reject) {
// 2.3.1. 如果 promise2 和 x 引用的是同一个对象,promise2 将以一个 TypeError 作为 reason 来进行 reject。
if (promise2 === x) {
return reject(new TypeError("Chaining cycle detected for promise"));
}
/**
// 与 2.3.3 有重叠部分
// 2.3.2. 如果 x 是一个 Promise,根据它的状态:
if (x instanceof Promise) {
// 2.3.2.1. 如果 x 的状态为 pending,Promise 必须保持 pending 状态直到 x 的状态变为 fulfilled 或 rejected。
if (x.state === "pending") {
x.then(
(value) => {
promiseResolutionHandler(promise2, value, resolve, reject);
},
reject
);
} else if (x.state === "fulfilled") {
// 2.3.2.2. 如果 x 的状态为 fulfilled,那么 promise2 也用同样的值来执行 fulfill 操作。
resolve(x.data);
} else if (x.state === "rejected") {
// 2.3.2.3. 如果 x 的状态为 rejected,那么 promise2 也用同样的 reason 来执行 reject 操作。
reject(x.data);
}
return;
}
*/
// 2.3.3. 除此之外,如果 x 是一个对象或者函数,
if (typeof x === "object" || typeof x === "function") {
// 如果 x 是 null,直接 resolve
if (x === null) {
return resolve(x);
}
// 2.3.3.3.3. 如果 resolvePromise 和 rejectPromise 都被调用,或者多次调用同样的参数,则第一次调用优先,任何之后的调用都将被忽略。
let isCalled = false;
try {
// 2.3.3.1. 声明一个 then 变量来保存 then
let then = x.then;
// 2.3.3.3. 如果 then 是一个函数,将 x 作为 this 来调用它,第一个参数为 resolvePromise,第二个参数为 rejectPromise,其中:
if (typeof then === "function") {
try {
then.call(
x,
// 2.3.3.3.1. 假设 resolvePromise 使用一个名为 y 的值来调用,运行 Promise 处理程序 [[Resolve]](promise, y)。
function resolvePromise(y) {
// 2.3.3.3.3. 如果 resolvePromise 和 rejectPromise 都被调用,或者多次调用同样的参数,则第一次调用优先,任何之后的调用都将被忽略。
if (isCalled) return;
isCalled = true;
promiseResolutionHandler(promise2, y, resolve, reject);
},
// 2.3.3.3.2. 假设 rejectPromise 使用一个名为 r 的 reason 来调用,则用 r 作为 reason 对 promise2 执行 reject 操作。
function rejectPromise(r) {
if (isCalled) return;
isCalled = true;
reject(r);
}
);
} catch (error) {
// 2.3.3.3.4. 如果调用 then 时抛出一个异常 e,
// 2.3.3.3.4.1. 如果 resolvePromise 或 rejectPromise 已经被调用过了,则忽略异常。
if (isCalled) return;
// 2.3.3.3.4.2. 否则,使用 e 作为 reason 对 promise2 执行 reject 操作。
reject(error);
}
} else {
// 2.3.3.4. 如果 then 不是一个函数,使用 x 作为值对 promise2 执行 fulfill 操作。
resolve(x);
}
} catch (error) {
// 2.3.3.2. 如果检索 x.then 的结果抛出异常 e,使用 e 作为 reason 对 promise2 执行 reject 操作。
return reject(error);
}
} else {
// 2.3.4. 如果 x 不是一个对象或者函数,使用 x 作为值对 promise2 执行 fulfill 操作。
resolve(x);
}
}
至此就完成了then()
的实现,需要引起注意的地方:
?
注意点一:可以看出同步任务不涉及 callback 的存储,异步任务会先进入宏任务队列,会在 JS 主栈空闲时执行存储的 callback, 核心实现其实就是——发布订阅模式
?
注意点二:链式调用的核心就是 .then()
返回一个新的 Promise
?
注意点三:**Promise**
值穿透
// 片段一
new Promise((resolve) => resolve(8))
.then()
.catch()
.then((value) => {
alert(value);
});
// 片段二
new Promise((resolve) => resolve(8))
.then((value) => {
return value;
})
.catch((reason) => {
throw reason;
})
.then((value) => {
alert(value);
});
片段一和片段二效果应该一样,如果**then()**
的实参留空且让值可以穿透到后面,只需要给**then()**
的两个参数设定默认值即可
onResolved =
typeof onResolved === "function"
? onResolved
: (value) => {
return value;
};
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason;
};
注意点四:**thenable 的核心逻辑 **参考promiseResolutionHandler
集中处理程序
.catch()
其实就是.then(null,()=>()}
class Promise {
// ...
/**
* catch方法
* @param {*} onRejected
* @returns
*/
catch(onRejected) {
return this.then(null, onRejected);
}
}
Promise.all()
class Promise {
// ...
/**
* Promise.all()方法
* @param {*} promiseArr
* @returns
*/
static all(promiseArr = []) {
return new Promise((resolve, reject) => {
// 记录成功的数量
let index = 0;
// 记录成功的结果
let result = [];
for (let i = 0; i < promiseArr.length; i++) {
promiseArr[i].then((val) => {
index++;
result[i] = val; // 保证结果的顺序和数组顺序一致
if (index === promiseArr.length) {
resolve(result);
}
}, reject);
}
});
}
}
小结
社区有很多实现,包括一些三方库的实现,还有其他语言基于 Promises/A+ 的实现:https://promisesaplus.com/implementations
?
完整版实现:https://github.com/shanejix/front-end-playground/blob/master/javascript/implement-promise/promise.js
?
测试结果:
new Promise()
处理同步任务和异步任务有所区别:同步任务会立即 resolve()
掉并修改 当前 promise 的状态。异步任务会预先存储 callback
(订阅事件),然后等待时机resolve()
掉 当前 promise ,核心思想就是 **发布订阅模式 **。
?
.then()
默认返回一个新的 promise :这是 promise 实现 **链式调用 **的核心
references
- https://promisesaplus.com/
- https://github.com/promises-aplus/promises-tests
- https://www.shanejix.com/posts/Promise%20%E5%BF%85%E7%9F%A5%E5%BF%85%E4%BC%9A/
- https://github.com/xieranmaya/blog/issues/3
- https://juejin.cn/post/6945319439772434469
- http://febook.hzfe.org/awesome-interview/book1/coding-promise
作者:shanejix
出处:https://www.shanejix.com/posts/Promises A+ implementation with ES6 class/
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
声明:转载请注明出处!