[JavaScript] 根据promiseA+规范手写实现promise
写在前面:关于promise的应用这里就不一一赘述了,本篇文章主要从代码层面,根据promiseA+规范,实现一个promise,文末有完整代码。
官方文档的阅读是很重要的,由于是英文写的,读起来比较困难,建议先熟悉promise的用法后再阅读官方文档,本篇文章并不会逐字翻译官方文档的内容。
这里贴上官方文档的地址:Promises/A+ (promisesaplus.com)
我们开始吧~
1. 基础概念
在开始实现之前我们先知道几个重要的基础概念,下面是一个基本的promise例子
let promise = new Promise((resolve, reject) => {
resolve("success");
// reject("Error");
// throw new Error("Error by throw")
}).then((value) => {
console.log(value);
}, (reason) =>{
console.log(reason);
})
1.1 执行器executor
在上面例子中,我们可以看到,Promise是一个构造函数,可以通过new实例化,实例化时会执行参数中的方法,这个方法就是执行器,执行器有两个参数,他们都是函数,一个是resolve方法,用于处理成功时的状态,一个是reject方法,用于处理失败时的状态
1.2 promise的三种状态
在文档中的2.1中写道:promise有三种状态:pending,fulfilled,rejected,pending可以转变为另外两种状态,但是另外两种状态无法再改变成其他状态
在fulfilled状态时,promise必须有value,value可以是任意数据类型,并且value是不可改变的,value如果是引用数据类型,则只是引用的地址不可改变。
在rejected状态时同理,promise必须有reason,reason可以理解为失败状态的原因。
1.3 then方法
文档2.2中写道:一个promise必须有then方法来接受value或reason,then方法的参数是两个回调函数:onFulfilled
和onRejected
,这两个参数是可选的且必须是函数。
onFulfilled
在promise变成fulfilled状态后被调用,并且只能被调用一次,他的第一个参数是前面说的value
onRejected
在promise变成rejected状态后被调用,也只能被调用一次,他的第一个参数是前面说的reason
2. 初步实现
到这里就可以实现一个最基本的promise了,使用的是ES6实现
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
// resolve和reject定义在constructor中,是因为每个promise都要有自己的resolve和reject,定义在外面就会变成每个pormise共用了
class myPromise {
// 执行器传入
constructor(executor) {
// 初始化状态为pending
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
// 定义resolve
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
}
}
// 定义reject
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
}
// 这里的try-catch是为了实现在paomise实例化时抛出异常,会调用rejected状态的回调函数的情况。
try {
executor(resolve, reject);
} catch(e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
}
}
// 导出
module.exports = myPromise;
测试一下:新建一个js文件,导入我们自己写的myPromise
let myPromise = require("./artical");
let promise1 = new myPromise((resolve, reject) => {
// 依次调用下面三行代码
resolve("onFulfilled");
// reject("onRejected")
// throw new Error("onRejected throw Error")
}).then((value) => {
console.log(value);
}, (reason) => {
console.log(reason)
})
小结: 实现一个最简单的promise,需要知道以下几个概念,将其串联起来即可
- promise的3个状态
- 执行器executor
- then方法和他的两个回调参数
- 执行器用try-catch包裹,用reject接管catch中的异常抛出
3. 处理异步和多次调用
3.1 异步的情况
还是用上面的测试用例,改成使用原生的promise
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("onFulfilled");
}, 2000)
}).then((value) => {
console.log(value); // 2s后输出"onFulfilled"
}, (reason) => {
console.log(reason)
})
原生的promise是可以支持异步调用的情况的,我们自己写的promise还不行,这是由于js代码有一套运行规则,运行到异步任务时,先挂起异步任务也就是上面的setTimeout
部分,此时promise的状态依旧是pending,然后接着去执行then中的代码,由于我们没有处理pending状态的方法,什么都不会发生。
另外,promise是可以多次调用then的,多次调用时要求按then的顺序从前到后依次调用。
因此我们需要处理一下pending状态 ,主要思路是发布-订阅模式:
- 使用数组存储then中的回调函数,即订阅
- 由于需要保存回调函数中传入的this.value或this.reason,因此保存时使用匿名函数包裹回调函数,形成一个闭包
- 异步最终也会调用resolve或reject,因此在这两个方法中进行按序调用数组中的回调函数,即发布
3.2 实现
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
// resolve和reject定义在constructor中,是因为每个promise都要有自己的resolve和reject,定义在外面就会变成每个pormise共用了
class myPromise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
// 存储then的回调函数参数
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
}
// 发布
this.onFulfilledCallbacks.forEach(fn => fn());
}
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
// 发布
this.onRejectedCallbacks.forEach(fn => fn());
}
// 这里的try-catch是为了实现在paomise实例化时抛出异常,会调用rejected状态的回调函数的情况。
try {
executor(resolve, reject);
} catch(e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
// 订阅
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
});
}
}
}
module.exports = myPromise;
测试一下:
let myPromise = require("./artical");
let promise1 = new myPromise((resolve, reject) => {
setTimeout(() => {
resolve("promise1")
}, 2000)
});
promise1.then((value) => {
console.log("第一次调用" + value + ".then");
}, (reason) => {
console.log(reason)
})
promise1.then((value) => {
console.log("第二次调用" + value + ".then")
})
4. 链式调用
在继续实现之前,我们需要先理解一下链式调用
4.1 成功和失败的条件
上面说道,promise成功时会走then中onFulfilled回调,失败时会走onRejected回调,最开始的成功失败是通过resolve和reject控制的,而在链式调用中,走哪一个回调需要具体分析
走onFulfilled的条件:(不管是在成功回调,或者是失败回调)
- then中通过return传递结果(注意函数默认返回undefined)
- then中通过新的promise调用resolve,即使resolve是异步的
走onRejected的条件:(不管是在成功回调,或者是失败回调)
- then中通过新的promise调用reject
- then中在onFulfilled回调中抛出异常
如果遇到catch,也可以把catch看成是成功回调为null的then方法
4.2 链式调用的实现思路
- 链式调用的本质其实是then方法会返回一个promise,在官方文档中指定了返回promise2,这是一个命名,我们按照官方文档来就好。
- onFulfilled和onRejected回调有返回值,这个返回值可能是普通值,也可能是一个promise,如果是promise则需要另外处理,把这个返回值定义为x
- 对于走4.1中的走onRejected的第二种情况,要用try-catch处理
- 定义一个新方法resolvePromise处理x,需要传递的参数有promise2,promise2的resolve和reject,以及x,并执行该方法(为什么要传递这些参数,后面会讲)
- 对于promise2,没有定义完成时是不能作为参数传递的,因此需要将resolvePromise变为异步任务,使得promise2先完成定义
4.3 实现
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
const resolvePromise = (promise2, x, resolve, reject) => {
// 测试打印一下拿到的数据
console.log("promise2:" + promise2);
console.log("x:" + x);
console.log("resolve:" + resolve);
console.log("reject:" + reject);
}
// resolve和reject定义在constructor中,是因为每个promise都要有自己的resolve和reject,定义在外面就会变成每个pormise共用了
class myPromise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
}
this.onFulfilledCallbacks.forEach(fn => fn());
}
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
this.onRejectedCallbacks.forEach(fn => fn());
}
// 这里的try-catch是为了实现在paomise实例化时抛出异常,会调用rejected状态的回调函数的情况。
try {
executor(resolve, reject);
} catch(e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
let promise2 = new myPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 异步处理
setTimeout(() => {
// 异常处理
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
}, 0)
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
})
}
if (this.status === PENDING) {
// 这里并不直接调用resolvePromise,因此不需要异步处理
this.onFulfilledCallbacks.push(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
this.onRejectedCallbacks.push(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
});
}
})
return promise2
}
}
module.exports = myPromise;
测试一下,看看resolvePromise能拿到什么
let myPromise = require("./artical");
let promise1 = new myPromise((resolve, reject) => {
setTimeout(() => {
resolve("promise1")
}, 2000)
});
let promise2 = promise1.then((value) => {
return value + "-> then -> promise2";
})
.then((value) => {
console.log(value);
}, () => {
})
5. resolvePromise方法
这一部分可以详细阅读文档,一步一步跟着来
- 为防止陷入死循环而报错,我们需要先判断x与调用then的promise是否是相同的引用,如果是,则调用reject抛出异常。说得再详细一点,x是在then中调用onFulfilled的返回值,而then也是一个promise调用的,如果返回值与这个promise相同,就会陷入死循环。
- 看x是不是promise,首先判断它是不是一个对象或function,如果是再判断x.then是不是一个function,如果都满足则说明x是promise,如果有一个条件不满足则resolve(x)就可以了。文档中还需要注意的一点是,x.then()可能被截持而抛出异常,此时就需要try-catch处理一下。
- 文档中说道:x是一个promise时,要以x为this执行里面的then方法,then方法中成功回调参数为y,失败回调参数为r
- 用called变量控制当成功失败的回调都被调用时,应只执行第一个回调
- 实现透传:then中没有参数,也会把值传给下一个then,即回调函数有默认值
- 递归x是一个promise时,里面如果又返回一个promise怎么办,所以在3.的基础上,应该递归调用resolvePromise
实现(完整代码):
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called = false;
if ((typeof x === "object" && typeof x !== null) || typeof x === "function") {
try {
let then = x.then;
if (typeof then === "function") {
then.call(x, (y) => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, (r) => {
if (called) return;
called = true;
reject(r);
});
} else {
resolve(x);
}
} catch(e) {
if (called) return;
called = true;
reject(e)
}
} else {
resolve(x);
}
}
class myPromise {
constructor (exector) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
}
// 发布
this.onFulfilledCallbacks.forEach(fn => fn())
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
}
// 发布
this.onRejectedCallbacks.forEach(fn => fn())
}
try {
exector(resolve, reject);
} catch(e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => {return value};
onRejected = typeof onRejected === "function" ? onRejected : reason => {throw reason};
let promise2 = new myPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
}, 0)
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
}, 0)
}
if (this.status === PENDING) {
// 订阅
this.onFulfilledCallbacks.push(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
});
this.onRejectedCallbacks.push(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
})
}
})
return promise2
}
catch(rejcetedCallback) {
return this.then(null, rejcetedCallback);
}
}
module.exports = myPromise;
本篇文章就到这里啦,如有错误,欢迎指正!