Promise实现
实现过程中使用到的工具函数
/**
* 判断传入值是否为一个 对象或者函数
* @param { * } value 待判断的值
* @returns { Boolean }
*/
function isObjectOrFunction(value) {
return (
!isEqual(value, null) &&
(judgmentType(value, "object") || judgmentType(value, "function"))
);
}
/**
* 判断传入值是否为一个 promise对象 实例
* @param { * } value 待判断的值
* @returns { Boolean }
*/
function isPromise(value) {
return value instanceof myPromise;
}
/**
* 判断传入值和目标值是否相等
* @param { * } value 待判断的值
* @param { * } target 目标值
* @returns { Boolean }
*/
function isEqual(value, target) {
return value === target;
}
/**
* 判断传入值和目标类型是否相等
* @param { * } value 待判断的值
* @param { * } type 目标类型
* @param { Boolean } negation 是否取反判断
* @returns { Boolean }
*/
function judgmentType(value, type, negation = false) {
if (negation) {
return typeof value !== type;
}
return typeof value === type;
}
Promise构造函数初始化
-
从上图观察可看出,promise实例对象上存在着两个内置属性:PromiseState(状态)、PromiseResult(结果数据)。 -
创建Promise对象时,需要传入执行器函数(executor),执行器函数同步执行。执行器函数内部又包含两个参数resolve、reject 。这两个参数(函数)供用户用于修改Promise对象的状态。
-
在Promise构造函数初始化中,我们需要处理的点:Promise对象的两个内置属性、执行器函数(excutor)的同步执行、供用户修改Promise对象状态的resolve(),reject()函数。
实现
class myPromise {
// 声明Promise状态
static PENDING = "pending"; // 待处理(状态)
static FULFILLED = "fulfilled"; // 已兑现(状态)
static REJECTED = "rejected"; // 已拒绝(状态)
constructor(excutor) {
// 2.1.1 pengding状态的promise对象 其状态可以改变为 fulfilled / rejected 状态
this.PromiseState = myPromise.PENDING // 初始化 promise状态 为 pending状态
this.PromiseResult = undefined // 初始化 promise 结果
try {
// 执行器函数 excutor的(同步)执行 在其内部用户可调用resolve/reject来改变promise对象状态
// 这里需要使用bind()函数显式地设置 resolve,reject方法的this指向当前的promise对象, 以便保证resolve/reject的正确执行
excutor(this.resolve.bind(this), this.reject.bind(this))
} catch(error) {
// 捕获执行器函数执行时抛出的报错 显式调用reject 修改当前promise对象的状态 结果值为对应报错信息
this.reject(error)
}
}
/**
* 用于修改promise状态 为 fulfilled(已兑现状态)
* @param { * } result promise对象的结果值(PromiseResult)
*/
resolve(result) {
// 2.1.2 fulfilled状态的promise对象 不得改变为任何其他状态 同时必须具有一个结果值
// promise状态改变:只能从pending状态 改变为 fulfilled / rejected状态,promise状态一经改变 就不可变
if (!isEqual(this.PromiseState, myPromise.PENDING)) return // 如果 promise对象 不处于 pending状态,直接return 不再执行后续更改状态等相关逻辑
this.PromiseState = myPromise.FULFILLED // 修改promise对象状态
this.PromiseResult = result // 为promise对象的结果值 赋值
}
/**
* 用于修改promise状态 为 rejected(已拒绝状态)
* @param { * } reason promise对象的结果值(PromiseResult)
*/
reject(reason) {
// 2.1.3 rejected状态的promise对象 不得改变为任何其他状态 同时必须具有一个结果值(理由/拒因)
// promise状态改变:只能从pending状态 改变为 fulfilled / rejected状态,promise状态一经改变 就不可变
if (!isEqual(this.PromiseState, myPromise.PENDING)) return // 如果 promise对象 不处于 pending状态,直接return 不再执行后续更改状态等相关逻辑
this.PromiseState = myPromise.REJECTED // 修改promise对象状态
this.PromiseResult = reason // 为promise对象的结果值 赋值
}
}
Promise对象实例方法 then
const p = new Promise((resolve, reject) => {
resolve('success');
});
const p1 = new Promise((resolve, reject) => {
resolve('third success');
});
const thenable = () => {}
thenable.__proto__.then = (onFulfilled, onRejected) => {
onRejected('failed')
}
console.log(1);
p.then((res) => {
console.log('then1', res);
return 'twice success'
}).then((res) => {
console.log('then2', res);
return p1
}).then((res) => {
console.log('then3', res);
return {
then(onFulfilled, onRejected) {
onFulfilled('fourth success')
}
}
}).then((res) => {
console.log('then4', res);
return thenable
}).then((res) => {
console.log('then5', res);
}, (err) => {
console.log(2);
console.log(err)
})
console.log(3);
从上述代码以及对应输出可以看出then方法的使用可以传入两个 根据Promise实例对象状态执行的回调函数 的可选参数,根据参数回调(onFulfilled/onRejected)执行所返回的值来决定then方法执行(异步执行)所返回的Promise对象(待返Promise对象)状态,同时支持链式调用。
- 根据参数回调执行的返回值类型的不同,作出的状态改变操作也不同。
- 返回值为待返Promise对象时,需要抛出报错,修改待返Promise对象状态为rejected,报错信息为结果值。
- 返回值为Promise对象时,根据该Promise对象的状态来决定待返Promise对象的状态
- 返回值为对象/函数时,检索访问其自定义的then方法,如果访问成功,根据该方法执行的返回值决定待返Promise对象的状态。反之,则修改待返Promise对象状态为fulfilled,返回值为结果值。
- 返回值不符合上述情况时,则修改待返Promise对象状态为fulfilled,返回值为结果值。
- 在Promise对象实例 then方法的实现中,我们需要处理的点:接收两个可选参数回调、then方法的异步执行、返回一个新的Promise对象,支持链式调用、根据调用对象的状态执行对应参数回调、根据参数回调的返回值决定待返Promise对象的状态。
实现
class myPromise {
// 声明Promise状态
.......
this.handleFulfilledCb = []
this.handleRejectedCb = []
constructor(excutor) {.......}
resolve(result) {
....
this.handleFulfilledCb.forEach(cb => cb())
}
reject(reason) {
...
this.handleRejectedCb.forEach(cb => cb())
}
then(onFulfilled, onRejected) {
// 2.2.6 then方法支持链式调用
// 2.2.6.1 当promise对象状态改变为 fulfilled 时,所有相应 onFulfilled回调根据原始调用顺序执行
// 2.2.6.2 当promise对象状态改变为 rejected 时,所有相应 onRejected回调根据原始调用顺序执行
// 声明用于返回的promise对象
let promiseForReturn = new myPromise((resolve, reject) => {
// 根据当前promise对象的状态执行对应回调
switch(this.PromiseState) {
case myPromise.FULFILLED:
// 2.2.4 onFulfilled函数在执行上下文堆栈仅包含平台代码之前,不得调用(即需要异步调用)
queueMicrotask(() => {
try {
// 2.2.1.1 如果onFulfilled不是函数 则必须忽略它
if (judgmentType(onFulfilled, 'function', true)) {
// 2.2.7.3 如果当前promise对象的状态为fulfilled,且onFulfilled不是一个函数 修改.then方法返回的promise对象的状态为fulfilled,同时继承当前promise对象的结果值
// onFulfilled不是函数时,就直接调用resolv() 修改 .then()调用所返回值的状态以及对应结果值
resolve(this.PromiseResult)
} else {
// 2.2.2.1 如果onFulfilled是一个函数 则将当前promise对象的结果值作为第一个参数传入onFulfilled函数中执行
// 2.2.2.3 onFulfilled函数只能执行一次,不能被多次调用
// 2.2.5 onFulfilled必须作为一个函数调用执行(在严格模式下)
// 2.2.7.1 如果onFulfilled的执行 存在返回值,凭此返回值 来决定 .then方法返回的promise对象的状态以及结果值
resolvePromise(
promiseForReturn,
onFulfilled(this.PromiseResult),
resolve,
reject
)
}
} catch (error) {
// 2.2.7.2 如果onFulfilled函数的执行 抛出报错 则修改.then方法返回的promise对象的状态为rejected,并将报错作为其结果值
// 捕获onFulfilled函数执行中 抛出的报错 修改.then方法返回的promise对象状态为rejected,结果值为对应报错信息
reject(error)
}
})
break;
case myPromise.REJECTED:
// 2.2.4 onRejected函数在执行上下文堆栈仅包含平台代码之前,不得调用(即需要异步调用)
queueMicrotask(() => {
try {
// 2.2.1.2 如果onRejected不是函数 则必须忽略它
if (judgmentType(onRejected, 'function', true)) {
// 2.2.7.4 如果当前promise对象的状态为rejected,且onRejected不是一个函数 修改.then方法返回的promise对象的状态为fulfilled,同时继承当前promise对象的结果值(拒因)
// onRejected不是函数时,就直接调用resolv() 修改 .then()调用所返回值的状态以及对应结果值
reject(this.PromiseResult)
} else {
// 2.2.3.1 如果onRejected是一个函数 则将当前promise对象的结果值(拒因)作为第一个参数传入onRejected函数中执行
// 2.2.3.3 onRejected函数只能执行一次,不能被多次调用
// 2.2.5 onRejected必须作为一个函数调用执行(在严格模式下)
// 2.2.7.1 如果onRejected的执行 存在返回值,凭此返回值 来决定 .then方法返回的promise对象的状态以及结果值
resolvePromise(
promiseForReturn,
onRejected(this.PromiseResult),
resolve,
reject
)
}
} catch (error) {
// 2.2.7.2 如果onRejected函数的执行 抛出报错 则修改.then方法返回的promise对象的状态为rejected,并将报错作为其结果值
// 捕获onRejected函数执行中 抛出的报错 修改.then方法返回的promise对象状态为rejected,结果值为对应报错信息
reject(error)
}
})
break;
case myPromise.PENDING:
// 2.2.2.2 当前promise对象状态为 fulfilled 时,才执行onFulfilled函数,在这之前不执行onFulfilled
// 2.2.3.2 当前promise对象状态为 rejected 时,才执行onRejected函数,在这之前不执行onRejected
// 当promise对象状态仍处于初始状态时,将对应的回调的相关处理,存储到一个数组中,等到promise对象状态改变为 fulfilled 时,在拿出执行
this.handleFulfilledCb.push(() => {
queueMicrotask(() => {
try {
if (judgmentType(onFulfilled, 'function', true)) {
resolve(this.PromiseResult)
} else {
resolvePromise(
promiseForReturn,
onFulfilled(this.PromiseResult),
resolve,
reject
)
}
} catch (error) {
reject(error)
}
})
})
// 2.2.3.2 当前promise对象状态为 rejected 时,才执行onRejected函数,在这之前不执行onRejected
// 当promise对象状态仍处于初始状态时,将对应的回调的相关处理,存储到一个数组中,等到promise对象状态改变为 rejected 时,在拿出执行
this.handleRejectedCb.push(() => {
queueMicrotask(() => {
try {
if (judgmentType(onRejected, 'function', true)) {
reject(this.PromiseResult)
} else {
resolvePromise(
promiseForReturn,
onRejected(this.PromiseResult),
resolve,
reject
)
}
} catch (error) {
reject(error)
}
})
})
break;
}
})
// 2.2.7 .then方法调用必须返回一个新的promise对象
// 返回新的Promise对象
return promiseForReturn
}
}
/**
* 根据 onFulfilled/onRejected 函数执行的返回值,对 .then方法返回的promise对象 作出相应的状态改变操作
* @param { myPromise } promiseForReturn .then方法返回的 promise对象
* @param { * } handledResult onFulfilled/onRejected 函数执行的返回值
* @param { Function } resolve 修改 .then方法返回的 promise对象 状态为 fulfilled 的方法
* @param { Function } reject 修改 .then方法返回的 promise对象 状态为 rejected 的方法
*/
function resolvePromise(promiseForReturn, handleResult, resolve, reject) {
let customThenFn; // 存储自定义的then方法(属性)
// 2.3.1 如果 onFulfilled/onRejected 函数执行的返回值 指向 .then方法返回的promise对象 抛出对应报错
if (isEqual(handleResult, promiseForReturn)) {
// 2.3.2 如果 onFulfilled/onRejected 函数执行的返回值 是一个promise对象 ,根据这个promise对象的状态 执行相应的状态改变变更操作
// 2.3.2.1 如果该 promise对象(handleResult)处于pending状态,在其状态改变之前 .then方法返回的promise对象始终保持 pending 状态
// 2.3.2.2 当该 promise对象(handleResult)的状态变更为 fulfilled 时,让 .then方法返回的promise对象 "继承"对应的状态和结果值
// 2.3.2.3 当该 promise对象(handleResult)的状态变更为 rejected 时,让 .then方法返回的promise对象 "继承"对应的状态和结果值(拒因)
// 显式的调用 .then方法 完成对应处理
throw new TypeError("Chaining cycle detected for promise")
} else if (isPromise(handleResult)) {
handleResult.then(
result => resolvePromise(promiseForReturn, result, resolve, reject),
reason => reject(reason)
)
} else if ((isObjectOrFunction) {
// 2.3.3 如果返回值是一个对象或者函数
try {
// 2.3.3.1 获取返回值的 then 属性
customThenFn = handleResult.then
} catch (error) {
// 2.3.3.2 如果获取 then 属性时, 抛出报错 直接修改 .then方法返回的promise对象为rejected 结果值为对应报错信息
// 捕获 获取 then 属性时 抛出的报错 修改 .then方法返回的promise对象为rejected 结果值为对应报错信息
return reject(error)
}
if (judfmentType(customThenFn, 'function')) {
// 2.3.3.3 如果 then属性 是一个函数 就将返回值作为该自定义then函数的 this指向,第一个参数是 类resolve()方法,第二个参数是 类rejecte()方法 传入对应参数 并执行
// 2.3.3.3.3 如果 类resolve()方法 和 类reject()方法同时被调用 或者 任意一个被多次调用,则第一次调用优先,任何进一步的调用都将被忽略
let called = false; // 避免多次调用 优先采用首次调用并忽略剩下的调用
try {
customThenFn.call(
handleResult,
(result) => {
if (called) return // 如果已经被调用过 直接返回 不作后续处理
called = true // 表示已调用
// 2.3.3.3.1 如果 类resolve()方法的执行存在返回值,就根据返回值在作出相应处理
// 显式地调用resolvepromise() 处理 类resolve方法执行的返回值
resolvePromise(promiseForReturn, result, resolve, reject)
},
(reason) => {
if (called) return // 如果已经被调用过 直接返回 不作后续处理
called = true // 表示已调用
// 2.3.3.3.2 如果 类reject()方法的执行存在返回值(拒因)时,修改.then方法返回的promise对象状态为rejected,结果值为相应的返回值(拒因)
reject(reason)
}
)
} catch (error) {
// 2.3.3.3.4 如果自定义 then函数的调用 抛出报错 修改.then方法返回的promise对象状态为rejected,结果值为对应报错信息
// 2.3.3.3.4.1 如果该报错已被捕获过 则忽略它
// 2.3.3.3.4.2 反之则捕获报错 并作出相应处理
if (called) return; // 如果已经被调用过 直接返回 不作后续处理
called = true // 表示已调用
reject(error) // 捕获自定义 then函数的调用 抛出的报错 修改 .then方法返回的promise对象为rejected 结果值为对应报错信息
}
} else {
// 2.3.3.4 如果 then属性不是函数 修改.then方法返回的promise对象状态为fulfilled,结果值为返回值
resolve(handleResult)
}
} else {
// 2.3.4 如果 返回值 既不是对象也不是函数 修改.then方法返回的promise对象状态为fulfilled,结果值为返回值
resolve(handleResult)
}
}
完整代码
class myPromise {
static PENDING = "pending";
static FULFILLED = "fulfilled";
static REJECTED = "rejected";
constructor(excutor) {
this.PromiseState = myPromise.PENDING;
this.PromiseResult = undefined;
this.handleFulfilledCb = [];
this.handleRejectedCb = [];
try {
excutor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
resolve(result) {
if (!isEqual(this.PromiseState, myPromise.PENDING)) return;
this.PromiseState = myPromise.FULFILLED;
this.PromiseResult = result;
this.handleFulfilledCb.forEach((cb) => cb());
}
reject(reason) {
if (!isEqual(this.PromiseState, myPromise.PENDING)) return;
this.PromiseState = myPromise.REJECTED;
this.PromiseResult = reason;
this.handleRejectedCb.forEach((cb) => cb());
}
then(onFulfilled, onRejected) {
let promiseForReturn = new myPromise((resolve, reject) => {
switch (this.PromiseState) {
case myPromise.FULFILLED:
queueMicrotask(() => {
try {
if (judgmentType(onFulfilled, "function", true)) {
resolve(this.PromiseResult);
} else {
resolvePromise(
promiseForReturn,
onFulfilled(this.PromiseResult),
resolve,
reject
);
}
} catch (error) {
reject(error);
}
});
break;
case myPromise.REJECTED:
queueMicrotask(() => {
try {
if (judgmentType(onRejected, "function", true)) {
reject(this.PromiseResult);
} else {
resolvePromise(
promiseForReturn,
onRejected(this.PromiseResult),
resolve,
reject
);
}
} catch (error) {
reject(error);
}
});
break;
case myPromise.PENDING:
this.handleFulfilledCb.push(() => {
queueMicrotask(() => {
try {
if (
judgmentType(onFulfilled, "function", true)
) {
resolve(this.PromiseResult);
} else {
resolvePromise(
promiseForReturn,
onFulfilled(this.PromiseResult),
resolve,
reject
);
}
} catch (error) {
reject(error);
}
});
});
this.handleRejectedCb.push(() => {
queueMicrotask(() => {
try {
if (
judgmentType(onRejected, "function", true)
) {
reject(this.PromiseResult);
} else {
resolvePromise(
promiseForReturn,
onRejected(this.PromiseResult),
resolve,
reject
);
}
} catch (error) {
reject(error);
}
});
});
break;
}
});
return promiseForReturn;
}
}
function resolvePromise(promiseForReturn, handledResult, resolve, reject) {
let customThenFn;
if (isEqual(handledResult, promiseForReturn)) {
throw new TypeError("Chaining cycle detected for promise");
} else if (isPromise(handledResult)) {
handledResult.then(
(result) =>
resolvePromise(promiseForReturn, result, resolve, reject),
(reason) => reject(reason)
);
} else if (isObjectOrFunction(handledResult)) {
try {
customThenFn = handledResult.then;
} catch (error) {
return reject(error);
}
if (judgmentType(customThenFn, "function")) {
let called = false;
try {
customThenFn.call(
handledResult,
(result) => {
if (called) return;
called = true;
resolvePromise(
promiseForReturn,
result,
resolve,
reject
);
},
(reason) => {
if (called) return;
called = true;
reject(reason);
}
);
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
resolve(handledResult);
}
} else {
resolve(handledResult);
}
}
myPromise.deferred = function () {
let result = {};
result.promise = new myPromise((resolve, reject) => {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
module.exports = myPromise;
测试
使用 promises-aplus-tests-refreshed 进行测试 仅测试Promise A+规范中的相关标准
- 安装
npm i promises-aplus-tests-refreshed
- 对外暴露myPromise类 并为myPromise类配置deferred属性方法
myPromise.deferred = function () {
let result = {};
result.promise = new myPromise((resolve, reject) => {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
module.exports = myPromise;
- 设置配置文件(packge.json)
"scripts": {
"test": "promises-aplus-tests-refreshed 文件路径"
},
- 终端运行 npm run test 命令即可开始测试
Promise对象实例方法 catch
- Promise实例的catch方法用于注册一个在Promise对象被拒绝时调用的函数,返回一个等效的Promise对象。支持链式调用。实质是Promise.prototype.then(undefined, onRejected)的语法糖。
实现
class myPromise {
.....
/**
* .then(undefined, onRejected) 的语法糖
* @param { Function } onRejected promise调用对象状态为 rejected 所调用的相应回调函数
* @returns { myPromise }
*/
catch(onRejected) {
return this.then(undefined, onRejected)
}
}
// 测试
const p1 = new myPromise((resolve, reject) => {
resolve("成功!");
});
p1.then((value) => {
console.log(value); // "成功!"
throw new Error("噢,不!");
})
.catch((e) => {
console.error(e.message); // "噢,不!"
})
.then(
() => console.log("在 catch 后,调用链恢复了"),
() => console.log("因为有了 catch 而不会被触发")
);
const p2 = new myPromise((resolve, reject) => {
setTimeout(() => {
throw new Error("未捕获的异常!");
}, 1000);
});
p2.catch((e) => {
console.error(e); // 永远不会被调用
});
const p3 = new myPromise((resolve, reject) => {
resolve();
throw new Error("Silenced Exception!");
});
p3.catch((e) => {
console.error(e); // 这里永远不会执行
});
Promised对象实例方法 finally
- Promise实例finally方法用于注册一个在Promise对象状态敲定(兑现/拒绝)时调用的函数,它会立即返回一个等效的Promise对象,支持链式调用。可以用于处理一些无关Promise对象状态的重复操作。
- 注意:所返回的Promise对象与调用对象等效,意味着无论传入的回调函数返回任何内容,待返promise对象依旧”继承”调用对象的相关状态和结果值。
- Promise对象实例方法 finally的实现,我们需要处理的点:无论调用对象处于何种状态,依旧执行参数回调、返回一个Promise对象,支持链式调用。
实现
class myPromise{
.....
/**
* 无论调用对象处于何种状态,始终调用参数回调
* @param { Function } onFinally
* @returns { myPromise }
*/
finally(onFinally) {
if (typeof onFinally !== 'function') {
onFinally = () => onFinally
}
const p = this.constructor // 获取当前实例的构造函数引用
// onFinally回调函数的执行 不影响用于返回的promise对象的状态
// 返回的promise对象状态及结果 继承自this指向的promise对象
// 使用实例对象上的静态方法resolve() 将onFinally的返回值 或执行 统一转换成一个Promise对象。
// p.resolve(onFinally())的处理 用于应对onFinally中存在的异步操作,可以确保onFinally函数的完全执行完毕,再去处理待返Promise对象的状态
return this.then(
(result) => p.resolve(onFinally()).then(() => result),
(reason) => p.resolve(onFinally()).then(() => { thorw reason })
)
}
}
// 测试
function p1() {
return new myPromise((resolve, reject) => {
setTimeout(() => {
reject("p1 rejected");
}, 1000)
});
}
function p2() {
return new myPromise((resolve, reject) => {
resolve('p2 resolve')
});
}
p2().finally(() => {
console.log('finally');
}).then(
res => console.log(res),
err => console.log(err)
)
p1().finally(() => {
console.log('finally');
}).then(
res => console.log(res),
err => console.log(err) // 等待一秒后输出
)
以上代码中使用到的 Promise静态方法 resolve 在该文章篇幅中尚未实现!
Promise静态方法的实现
参考
手把手一行一行代码教你“手写Promise“,完美通过 Promises/A+ 官方872个测试用例
Promises/A+ (promisesaplus.com)
Promise – JavaScript | MDN (mozilla.org)
第 64 题:模拟实现一个 Promise.finally · Issue #109 · Advanced-Frontend/Daily-Interview-Question · GitHub
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END