在上篇文章中已经完成了Promise的then
方法的基本实现。但是还有一个Promise A+中的核心要求没有实现。那就是Promise的解析过程
。它是Promise A+规范中最为复杂的部分。因为它要确保不同Promise实现之间的互操作性,因此它的判断的情况也很多。也是理解Promise链式调用的关键。此文将按照Promise A+规范的要求一步一步地实现。
什么是Promise的解析过程?
Promise的解析过程采用递归算法,用于处理then方法返回的值。主要作用:
处理thenable对象
:确保不同Promise实现可以互操作防止循环引用
:避免Promise链中出现无限循环统一值处理
:对基本类型、对象、Promise实例进行统一处理
先搭建一个解析Promise的函数
/**
* Promise的解析过程x
* @param {Promise} then新创建的promise实例
* @param {*} then方法回调函数返回的值
* @param {Function} promise的resolve函数
* @param {Function} promise的reject函数
*/
function resolvePromise(promise, x, resolve, reject) {
// 解析过程在这里处理
}
因此上篇文章代码稍微调整一点点
(function () {
"use strict";
/* 其他代码省略 */
definePropertyConfig(prototype, "then", function (onFulfilled, onRejected) {
/* 其他代码省略 */
// 将 return new Promise)() 改为 var promise = new Promise(); return promise
// 以便 将 promise 传入 handleCallback
var promise = new Promise(function (resolve, reject) {
function handleCallback(value, callback, resolve, reject) {
setTimeout(function () {
try {
var result = callback(value);
// 在此处进行Promise的解析
resolvePromise(promise, result, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (self.state === FULFILLED) {
handleCallback(self.result, onFulfilled, resolve, reject);
} else if (self.state === REJECTED) {
handleCallback(self.result, onRejected, resolve, reject);
} else {
self.onFulfilledCallbacks.push(function (value) {
handleCallback(value, onFulfilled, resolve, reject);
});
self.onRejectedCallbacks.push(function (value) {
handleCallback(value, onRejected, resolve, reject);
});
}
});
return promise
});
防止循环引用
Promise A+规范解析过程的第一步: 如果 promise 和 x 引用同一个对象,则以 TypeError 拒绝 promise。为什么需要这样呢?看一段如下代码:
var p = new Promise(function (resolve) {
resolve();
});
var thenable = {
then: function (resolve) {
resolve(thenable); // 这里会造成循环引用
}
};
let p2 = p.then(function () {
return p2; // 如果没有检查,将导致无限递归
});
由上述代码可以轻松地编写如下代码
resolvePromise(promose, x, resolve, reject) {
if (x === promise) {
throw new TypeError('Chaining cycle detected fro promise #<Promise>');
}
}
处理对象和函数的情况
需要判断x是否是一个对象或者函数。
- 如果是对象,获取对象属性then,然后判断属性then是否是一个
Function
类型的 - 如果对象属性then是一个函数或者x是一个函数,则调用执行。
- 如果不是对象也不是函数的话,直接
resolve
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
throw new TypeError("Chaining cycle detected for promise #<Promise>");
}
if (x !== null && /^(object|function)$/.test(typeof x)) {
var then = void 0; // 存储then方法
try {
then = x.then;
} catch (error) {
reject(error);
return;
}
// 判断是否是一个函数,true则直接调用then方法
if (typeof then === "function") {
try {
then.call(
x,
function (y) {
// 递归调用 直到不是thenable
resolvePromise(promise, y, resolve, reject);
},
function (r) {
reject(r)
}
);
} catch (error) {
reject(error);
}
return;
}
}
// 其他情况直接resolve
resolve(x);
}
添加called标志
为什么需要添加called标志呢?看如下代码:
var dangerousThenable = {
then: function(resolve, reject) {
resolve('第一次调用');
resolve('第二次调用'); // 违反规范:多次调用
reject('错误调用'); // 违反规范:混合调用
}
};
为了避免上述情况,代码如下:
function resolvePromise(promise, x, resolve, reject) {
/* 前面代码省略 */
if (typeof then === "function") {
var called = false;
try {
then.call(
x,
function (y) {
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
function (r) {
if (called) return;
called = true;
reject(r);
}
);
} catch (error) {
if (called) return;
called = true;
reject(error);
}
return;
}
}
resolve(x);
}
测试代码是否符合Promise A+规范
至此Promise的手写告一段落。如何验证我们的代码是否符合Promise A+规范呢?可以通过promises-aplus-tests库来进行验证。
1. 安装promises-aplus-tests
npm i promises-aplus-tests -D
2. 添加测试代码
(function () {
"use strict";
/*上面代码省略*/
/*测试代码*/
Promise.deferred = function () {
var result = {};
result.promise = new Promise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
// 环境检测与导出
if (typeof window !== "undefined") {
window.Promise = Promise;
}
if (typeof module === "object" && typeof module.exports === "object") {
module.exports = Promise;
}
})();
3. 添加测试运行指令
在package.json中添加运行指令
{
"script": {
// 测试文件路径需要根据自己项目确定
"test": "promises-aplus-tests ./src/index.js"
}
}
4. 终端输入指令测试
npm run test
输入指令后正常情况,872条测试用例都可以通过。如图所示:
完整代码示例
(function () {
"use strict";
var PENDING = "pending";
var FULFILLED = "fulfilled";
var REJECTED = "rejected";
function Promise(executor) {
var self = this;
// 参数校验
if (typeof executor !== "function") {
throw new TypeError(`Promise resolver ${executor} is not a function`);
}
if (!(self instanceof Promise)) {
throw new TypeError("Promise constructor must be called with 'new'");
}
self.state = PENDING;
self.result = void 0;
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
var changeState = function (state, value) {
if (self.state !== PENDING) return;
self.state = state;
self.result = value;
var callbacks =
state === FULFILLED
? self.onFulfilledCallbacks
: self.onRejectedCallbacks;
setTimeout(function () {
callbacks.forEach(function (callback) {
typeof callback === "function" && callback(self.result);
});
});
};
var resolve = function (value) {
changeState(FULFILLED, value);
};
var reject = function (reason) {
changeState(REJECTED, reason);
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
var prototype = Promise.prototype;
function definePropertyConfig(object, key, value) {
Object.defineProperty(object, key, {
value: value,
writable: true,
enumerable: false,
configurable: true,
});
}
function checkPromiseInstance(value) {
if (!(value instanceof Promise)) {
throw new TypeError(
"Method Promise.prototype.then called on incompatible receiver #<Promise>"
);
}
}
var resolvePromise = function (promise, x, resolve, reject) {
if (promise === x) {
throw new TypeError("Chaining cycle detected for promise #<Promise>");
}
if (x !== null && /^(object|function)$/.test(typeof x)) {
var then = void 0;
try {
then = x.then;
} catch (error) {
reject(error);
return;
}
if (typeof then === "function") {
var called = false;
try {
then.call(
x,
function (y) {
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
function (r) {
if (called) return;
called = true;
reject(r);
}
);
} catch (error) {
if (called) return;
called = true;
reject(error);
}
return;
}
}
resolve(x);
};
definePropertyConfig(prototype, "then", function (onFulfilled, onRejected) {
checkPromiseInstance(this);
var self = this;
// 值穿透处理
onFulfilled =
typeof onFulfilled === "function"
? onFulfilled
: function (value) {
return value;
};
onRejected =
typeof onRejected === "function"
? onRejected
: function (reason) {
throw reason;
};
function handleCallback(value, callback, resolve, reject) {
try {
var result = callback(value);
resolvePromise(promise, result, resolve, reject);
} catch (error) {
console.error(error);
reject(error);
}
}
var promise = new Promise(function (resolve, reject) {
if (self.state === FULFILLED) {
setTimeout(function () {
handleCallback(self.result, onFulfilled, resolve, reject);
}, 0);
} else if (self.state === REJECTED) {
setTimeout(function () {
handleCallback(self.result, onRejected, resolve, reject);
}, 0);
} else {
self.onFulfilledCallbacks.push(function (value) {
handleCallback(value, onFulfilled, resolve, reject);
});
self.onRejectedCallbacks.push(function (value) {
handleCallback(value, onRejected, resolve, reject);
});
}
});
return promise;
});
/*测试代码*/
Promise.deferred = function () {
var result = {};
result.promise = new Promise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
// 环境检测与导出
if (typeof window !== "undefined") {
window.Promise = Promise;
}
if (typeof module === "object" && typeof module.exports === "object") {
module.exports = Promise;
}
})();
小结
通过本文的深入探讨,我们完整实现了 Promise A+ 规范中最核心且复杂的解析过程。这不仅是一个技术实现,更是对 JavaScript 异步编程深刻理解的体现。
Promise 的解析过程是异步编程的基石之一,掌握它意味着你对 JavaScript 异步世界的理解达到了新的高度。继续探索,保持好奇,愿你在异步编程的道路上越走越远!
接下来我们将继续完善Promise。如示例方法:catch
、finally
;静态方法:resolve
、reject
all
reace
等;
宝剑锋从磨砺出,Promise 妙自解析来 ✨ 期待在一篇文章中与你再见!