【手写 Promise】-- 深入理解 Promise

【手写Promise】-- 深入理解Promise

在此之前,我对于 Promise 有些不求甚解,回顾了我之前写的 手写Promise 这篇文章,发现其错漏百出,我直接 “呵tui!” 。

在写这篇文章之前,我看了很多网上的 “手写Promise” 代码,它们大多有以下的问题:

  1. 回调的代码(then 方法中的函数参数)执行位置错误。比如我之前写的那篇。(回调的代码应该存入回调队列中,在 resolve 中需要清空一次回调队列;并且在 then 方法中,Promise 状态不为 PENDING 的情况下直接执行回调。)
  2. 为了使 then 方法中传入的函数异步执行,许多代码实现中使用了setTimeout对代码进行封装,但是setTimeout包装的代码是宏任务,而实际上 then 方法中传入的函数是作为微任务被异步执行的,所以使用 process.nextTick 将代码包装为微任务应该会更为合理。
  3. 在实现 all 方法时直接调用 then 方法向回调队列中插入回调事件,这样在 Promise 的执行中,先发生状态改变的 Promise 会首先向结果数组中存入执行结果,这样很可能导致 all 方法接收的 Promise 数组参数与最终返回的结果数组中数据的顺序不匹配(出现竞速)。存在这个错误的比如这个手写实现Promise

刚好今天“前端大全”公众号给我推了一篇介绍 async 的文章,并且有提到一个手写 Promise 的实现,我认为这个是我目前看到的手写实现 Promise 的 polyfill 中比较完善的(应该会比我接下来实现的 polyfill 更为完善),链接如下:

https://github.com/19Qingfeng/notes/blob/master/promise/core/index.js

默认大家此前对 Promise 的使用有了一些基本的了解,接下来我会通过实现 Promise 的 polyfill 代码来作为对于 Promise 理解之后的成果。在实现之前先记录几个便于理解和实现 Promise 的几个注意点。

  1. Promise 中的状态有 初始状态’PENDING’、结果状态’FULFILLED’和’REJECT’ ,Promise 的状态不受外界影响,只受异步操作的结果决定,并且一旦变更为结果状态,状态就不会再改变了。
  2. resolve 方法和 reject 方法是内置的用于改变 Promise 状态的函数。
  3. resolve 方法的参数除了是一个普通的值外,还可能时另一个 Promise。如下所示:
var p1 = new Promise (function (resolve, reject) {
	//...
}) ;
var p2 = new Promise (function (resolve, reject) {
	//...
	resolve(p1);
}) ;

上面的代码中,p1和p2都是Promise的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。此时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是Pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是Resolved或Rejected,那么p2的对应的回调函数将会立刻执行。

  1. then 方法会返回一个新的 Promise,基于此实现 Promise 的链式调用。

接下来就来尝试小试一下牛刀:

const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
const PENDING = 'pending';

// 既然是 polyfill 代码,那么用构造函数实现应该比 class 更加合理
function MyPromise(executor) {
	this.status = PENDING; 
	this.value = undefined;
	this.reason = undefined;
	this.successCallback = []; // resolve 状态的回调队列
	this.failureCallback = []; // reject 状态的回调队列

	const self = this;
	const resolve = (value) => {
		// 如果 value 是一个 Promise ,那么就依据 value 的状态来调用相应的回调
		if (value instanceof MyPromise) {
			// 根据 value 的状态,调用当前 promise 的 resolve 或 reject
			return value.then(resolve, reject); 
		}
		// value 为普通值
		if (self.status === PENDING) {
			self.status = FULFILLED;
			self.value = value;
			self.successCallback.forEach(fn => fn(value));
		}
	}
	const reject = (reason) => {
		if (self.status === PENDING) {
			self.status = REJECTED;
			self.reason = reason;
			self.failureCallback.forEach(fn => fn(value));
		}
	}

	try {
		executor(resolve, reject)
	} catch (err) {
		reject(err);
	}
}

// 实现 then 方法,并支持链式调用(返回一个新的 Promise)
// 因为 onFulfilled(this.value) 的结果(记为 x)会作为下一次 then 方法的 onFulfilled 回调的参数。
// 如果 x 是普通值,直接调用 resolve;如果 x 是一个 PENDING 状态的 Promise,则 x 需保持为等待态直至 x 被执行或拒绝;
// 如果 x 是一个 FULFILLED 或 REJECTED 状态的 Promise,则用同样的方法递归处理 x 
// 下面会专门写一个函数 resolvePromise 来判断 x 的值以及状态,并给出相应的处理
MyPromise.prototype.then = function(onFulfilled, onRejected) {
	const self = this; // 用 self 保存 this,避免后期闭包导致 this 的指向不对
	// 设置默认回调函数
	onFulfilled = typeof onFulfilled === 'function' ? onFulfilled  : res => res;
	onRejected= typeof onFulfilled === 'function' ? onRejected : (err) => { throw err };
	
	let nextPromise = new Promise((resolve, reject) => {
		if (self.status === PENDING) {
			self.successCallback.push(() => {
				process.nextTick(() => {
					try {
						const x = onFulfilled(self.value);
						// 接下来判断这个 x 是不是 promise,以及它的状态; 并依此判断调用 resolve 或  reject 或 继续递归调用 resolvePromise
						resolvePromise(nextPromise, x, resolve, reject);
					} catch (err) {
						reject(err);
					}
				});
			});
			self.failureCallback.push(() => {
				process.nextTick(() => {
					try {
						const x = onRejected(self.reason);
						resolvePromise(nextPromise, x, resolve, reject);
					} catch (err) {
						reject(err);
					}
				});
			});
		}
		if (self.status === FULFILLED) {
			process.nextTick(() => {
				try {
					const x = onFulfilled(self.value);
					resolvePromise(nextPromise, x, resolve, reject);
				} catch (err) {
					reject(err);
				}
			});
		}
		if (self.status === REJECTED) {
			process.nextTick(() => {
				try {
					const x = onRejected(self.reason);
					resolvePromise(nextPromise, x, resolve, reject);
				} catch (err) {
					reject(err);
				}
			});
		}
	});
	return nextPromise;
}

/* 核心代码 (主要就是为了实现 then 的链式调用)
nextPromise: then 方法返回的新 promise
x: 当前 then  中回调的执行结果,(如果不为 Promise,也是下一次调用 then 的回调的参数)
resolve: 新 promise 的 resolve 方法
reject:  新 promise 的 reject 方法
*/
function resolvePromise(nextPromise, x, resolve, reject) {
	// 禁止循环引用
	if (nextPromise === x) {
		return reject(new TypeError("循环引用!"));
	}
	// x 是 thenable 时(鸭子类型),只有 object 或 function 类型的数据可能有 then 方法。在 x 为其他类型的情况下,如果直接访问 x.then 的话,是会报类型错误的。这也是我们接下来要进行下一步判断的原因
	let called = false; // 这里是为了确保只调用一次 resolve 或 reject 方法。避免用户使用时同时调用这两个方法,或多次调用其中一个方法,而导致的回调队列被多次执行
	if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
		try {
			let then = x.then;
			// x 是 Promise 或 thenable 对象
			if (typeof then === 'function') { 
				// 如果 x 是Promise, 在ES规范中需要被推迟到下一次loop中去调用then
				process.nextTick(() => {
					// 为什么这里要在 x 的上下文环境下(即 this 指向 x)调用 then 方法?为了根据 x 的状态,来决定调用 Fulfilled的回调 还是 Reject的回调
					then.call(x, (v) => {
						if (called) return;
						called = true;
						resolvePromise(x, v, resolve, reject);
					}, (e) => {
						if (called) return;
						called = true;
						reject(e);
					})
				});
			} else {
				// x 不是普通类型的值,但是它没有 then 方法,因此不用进行等待,直接 resolve
				if (called) return;
				called = true;
				resolve(x);
			}
		} catch(err) {
			if (called) return;
			called = true;
			reject(err);
		}
	} else {
		// x 是普通类型的值,就可以直接进行状态更改,不用进行等待
		if (called) return;
		called = true;
		resolve(x);
	}
}

接下来实现 静态方法 resolve、reject、all 和 race

MyPromise.resolve = (value) => {
	return new MyPromise((resolve, reject) => {
		resolve(value);
	});
}

MyPromise.reject = (reason) => {
	return new MyPromise((resolve, reject) => {
		reject(reason);
	});
}
/* all 方法的实现逻辑
功能:接收一个 Promise 数组,等数组中的每个 Promise 都执行完且都处于 FULFILLED 状态之后,执行 resolve 方法,并将每个 Promise 执行结果构成的数组作为 resolve 的参数。一旦有任意一个 Promise 处于 REJECT 状态就会立刻执行 reject 方法,reject 的参数为这个处于 REJECT 状态的 Promise 的 reject 方法的参数。
逻辑:每个 Promise 状态变为 FULFILLED 状态之后(即调用 resolve 之后),都会向结果数组的相应位置添加一个结果value,并判断数组是否填满(即所有 Promise 是否都已经 FULFILLED 了),一旦数组填满,就会使用这个数组作为参数调用 resolve 方法来执行 then 方法添加的回调。
*/
MyPromise.all = (promises) => {
	const values = [];
	let count = 0;
	return new MyPromise((resolve, reject) => {
		if (promises.length === 0){
    		return resolve(values);
    	}
		promises.map(p => {
			// 如果数组成员不是 Promise 实例,就先使用 MyPromise.resolve 对其进行一次包装
			return (p instanceof MyPromise) ? p : MyPromise.resolve(p);
		}).forEach((promise, i) => {
			promise.then((value) => {
				values[i] = value;
				if (++count === promises.length) {
					resolve(values);
				}
			}, (err) => {
				reject(err);
			})
		});
	});
}

MyPromise.race = (promises) => {
	return new MyPromise((resolve, reject) => {
		if (promises.length === 0){
    		return resolve();
    	}
		promises.map(p => {
			// 如果数组成员不是 Promise 实例,就先使用 MyPromise.resolve 对其进行一次包装
			return (p instanceof MyPromise) ? p : MyPromise.resolve(p);
		}).forEach((promise, i) => {
			promise.then(resolve, reject);
		});
	});
}

今天在掘金上看到了一篇 Promise.all 的实现相关的文章,也可以稍作参考:

https://juejin.cn/post/7069805387490263047

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值