实现符合A+规范的Promise

本文最后更新于:2022年5月31日 早上

promise

Promise 对象用于处理异步操作,它表示一个尚未完成且预计在未来完成的异步操作。

特性

  • 解决回调地狱,使用统一的 API 接口处理异步任务
  • 链式调用
  • 返回值穿透(onFulfilled 不是 function 时,上一步的执行结果 value 将继续向后传递)
  • 错误冒泡(

回调地狱

function f1(){
  //f2的执行需要等待f1的结果
  return f2(){
     f3(){
      f4(){
        f5(){
          ...
        }
        ...
      }
    }
  }
}

Promise 对象有以下两个特点

  1. 对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。如果改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

方法

Promise.prototype.then()

  • 为 Promise 实例添加状态改变时的回调函数
  • then 方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数
  • then 方法返回的是一个新的 Promise 实例,可以 then 方法后面再调用另一个 then 方法
getJSON("/post/1.json")
	.then(function (post) {
		return getJSON(post.commentURL);
	})
	.then(
		function funcA(comments) {
			console.log("resolved: ", comments);
		},
		function funcB(err) {
			console.log("rejected: ", err);
		}
	);
// 第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用funcA,如果状态变为rejected,就调用funcB。

Promise.prototype.catch()

  • 指定发生错误时的回调函数

  • Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获

// 一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。
// bad
promise.then(
	function (data) {
		// success
	},
	function (err) {
		// error
	}
);

// good
promise
	.then(function (data) {
		//cb
		// success
	})
	.catch(function (err) {
		// error
	});

跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应

  • catch 方法返回的还是一个 Promise 对象,因此后面还可以接着调用 then 方法

Promise.prototype.finally()

  • finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作

  • finally 方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是 fulfilled 还是 rejected。这表明,finally 方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

Promise.all()

  • Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例

const p = Promise.all([p1, p2, p3]);

  • p 的状态由 p1、p2、p3 决定,分成两种情况。
  1. 只有 p1、p2、p3 的状态都变成fulfilled,p 的状态才会变成fulfilled,此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数。

  2. 只要 p1、p2、p3 之中有一个被rejected,p 的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给 p 的回调函数

如果作为参数的 Promise 实例,自己定义了 catch 方法,那么它一旦被 rejected,并不会触发 Promise.all()的 catch 方法

Promise.race()

Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。

//example
const p = Promise.race([
	fetch("/resource-that-may-take-a-while"),
	new Promise(function (resolve, reject) {
		setTimeout(() => reject(new Error("request timeout")), 5000);
	}), // 5s内请求不resolve则抛出错误
]);

p.then(console.log).catch(console.error);

promise.try()

不想区分,函数 f 是同步函数还是异步操作,但是想用 Promise 来处理它

try {
  database.users.get({id: userId})
  .then(...)
  .catch(...) // promise错误
} catch (e) { // 同步错误
  // ...
}
// try catch捕获同步错误
// 用promise.try改写
Promise.try(() => database.users.get({id: userId}))
  .then(...)
  .catch(...)

tips

  • 立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行

Promise.resolve()方法允许调用时不带参数,直接返回一个 resolved 状态的 Promise 对象。

setTimeout(function () {
	console.log("three");
}, 0);

Promise.resolve().then(function () {
	console.log("two");
});

console.log("one");

// one
// two
// three
  • Promise.reject()方法的参数,会原封不动地作为 reject 的理由,变成后续方法的参数

    const thenable = {
    	then(resolve, reject) {
    		reject("出错了");
    	},
    };
    
    Promise.reject(thenable).catch((e) => {
    	console.log(e);
    });
    // catch方法的参数不是reject抛出的“出错了”这个字符串,而是thenable对象。

手写 Promise

  • 测试 a+规范
promises-aplus-tests
node promises-aplus-tests yourpeomise.js

// yourpromise.js
MyPromise.defer = MyPromise.deferred = function () {
	let dfd = {};
	dfd.promise = new MyPromise((resolve, reject) => {
		dfd.resolve = resolve;
		dfd.reject = reject;
	});
	return dfd;
};
module.exports = MyPromise
  • resolvePromise
const isComplex = (obj) => obj !== null && (typeof obj === "function" || typeof obj === "object");
// 判断then之后的状态
function resolvePromise(x, promise2, resolve, reject) {
	if (x === promise2) {
		reject(new TypeError("Chaining cycle detected for promise"));
	}
	if (isComplex(x)) {
		let hasRes = false;
		try {
			const then = x.then;
			// 如果x是promise,继续判断
			if (typeof then === "function") {
				then.call(
					x,
					(v) => {
						if (hasRes) return;
						hasRes = true;
						resolvePromise(v, promise2, resolve, reject);
					},
					(r) => {
						if (hasRes) return;
						hasRes = true;
						reject(r);
					}
				);
			} else {
				// 如果x是个对象
				resolve(x);
			}
		} catch (e) {
			if (hasRes) return;
			hasRes = true;
			reject(e);
		}
	} else {
		resolve(x);
	}
}

// 通用常量
const PENDING = Symbol('PENDING');
const FULFILLED = Symbol('FULFILLED');
const REJECTED = Symbol('REJECTED');
  • function 版
    s

    function MyPromise(executor) {
    	this.status = PENDING;
    	this.data = undefined;
    	this.onFulfilledCb = [];
    	this.onRejectedCd = [];
    
    	const resolve = (value) => {
    		setTimeout(() => {
    			// 放在下个宏任务等promise2都执行得出状态
    			if (this.status === PENDING) {
    				this.status = FULFILLED;
    				this.data = value;
    				this.onFulfilledCb.forEach((cb) => cb());
    			}
    		});
    	};
    	const reject = (reason) => {
    		setTimeout(() => {
    			// 放在下个宏任务等promise2都执行得出状态
    			if (this.status === PENDING) {
    				this.status = REJECTED;
    				this.data = reason;
    				this.onRejectedCd.forEach((cb) => cb());
    			}
    		});
    	};
    
    	try {
    		executor(resolve, reject);
    	} catch (e) {
    		reject(e);
    	}
    }
    
    MyPromise.prototype.then = function (onFulfill, onReject) {
    	onFulfill = typeof onFulfill === "function" ? onFulfill : (value) => value;
    	onReject =
    		typeof onReject === "function"
    			? onReject
    			: (err) => {
    					throw err;
    			  };
    	let promise2 = new Promise((resolve, reject) => {
    		if (this.status === PENDING) {
    			// push进去的时候promise2还没有执行完
    			this.onFulfilledCb.push(() => {
    				try {
    					let x = onFulfill(this.data);
    					resolvePromise(x, promise2, resolve, reject);
    				} catch (e) {
    					reject(e);
    				}
    			});
    			this.onRejectedCd.push(() => {
    				try {
    					let x = onReject(this.data);
    					resolvePromise(x, promise2, resolve, reject);
    				} catch (e) {
    					reject(e);
    				}
    			});
    			// 由于new Promise构造函数逻辑
    			// 可以简写
    		} else if (this.status === FULFILLED) {
    			// 如果返回一个promise,依然需要判断他后续的状态,交给下一个then时去判断👆的代码
    			setTimeout(() => onFulfill(this.data)); 
    			// setTimeout(()=>{
    			// 	try{
    			// 		let x = onFulfill(this.data)
    			// 		resolvePromise(x,promise2,resolve,reject)
    			// 	}catch(e){
    			// 		reject(e)
    			// 	}
    			// })
    		} else if (this.status === REJECTED) {
    			setTimeout(() => onReject(this.data)); 
    		}
    	});
    
    	return promise2;
    };
  • class 版

class MyPromise {
	constructor(executor) {
		try {
			executor(this.resolve, this.reject);
		} catch (e) {
			this, reject(e);
		}
	}
	status = PENDING;
	data = undefined;
	successCallback = [];
	failCallback = [];

	resolve = (value) => {
		if (value instanceof MyPromise) {
			return value.then(this.resolve, this.reject);
		}
		setTimeout(() => {
			if (this.status === PENDING) {
				this.data = value;
				this.status = FULFILLED;
				this.successCallback.forEach((cb) => cb());
			}
		});
	};

	reject = (reason) => {
		setTimeout(() => {
			if (this.status === PENDING) {
				this.data = reason;
				this.status = REJECTED;
				this.failCallback.forEach((cb) => cb());
			}
		});
	};

	then(onResolve, onReject) {
		onResolve = typeof onResolve === "function" ? onResolve : (val) => val;
		onReject =
			typeof onReject === "function"
				? onReject
				: (err) => {
						throw err;
				  };
		
		const promise2 = new MyPromise((resolve,reject)=>{
			if(this.status === PENDING){
				this.successCallback.push(()=>{
					try{
						const x = onResolve(this.data)
						resolvePromise(x,promise2,resolve,reject)
					}catch(e){
						reject(e)
					}
				})
				this.failCallback.push(()=>{
					try{
						const x = onReject(this.data)
						resolvePromise(x,promise2,resolve,reject)
					}catch(e){
						reject(e)
					}
				})
			}else if(this.status === FULFILLED){
				setTimeout(()=>onResolve(this.data))
			}else if(this.status === REJECTED){
				setTimeout(()=>onReject(this.data))
			}
		})
		return promise2
	}
}
  • API
Promise.resolve = (value) => new Promise((resolve, reject) => resolve(value));
Promise.reject = (reason) => new Promise((resolve, reject) => reject(reason));
// catch
Promise.prototype.catch = (onReject) => {
	return this.then(null, onReject);
};
// finally
Promise.prototype.finally = function (callback) {
	return this.then(
		(value) => {
			Promise.resolve(callback()).then(() => value);
		},
		(reason) => {
			Promise.resolve(callback()).then(() => {
				throw reason;
			});
		}
	);
};
// race
Promise.race = function (pArr) {
	// 抛出第一个改变的状态
	return new Promise((resolve, reject) => {
		pArr.forEach((pItem) => pItem.then(resolve, reject));
	});
};
// any
Promise.any = function (pArr) {
	// 有resolve就resolve
	// 都没有resolve则返回一个由所有err组成的AggregateError对象
	return new Promise((resolve, reject) => {
		if (!pArr.length) return reject(new AggregateError([]));
		const res = [];
		pArr.forEach((pItem, index) => {
			pItem.then(
				(v) => {
					resolve(v);
				},
				(e) => {
					res[index] = e;
				}
			);
		});
		reject(new AggregateError(res));
	});
};

// all
Promise.all = function (pArr) {
	// resolve全部value
	// 有err就reject
	return new Promise((resolve, reject) => {
		if (!pArr.length) return resolve([]);
		const res = [];
		pArr.forEach((pItem, index) => {
			pItem.then(
				(v) => {
					res[index] = v;
				},
				(e) => {
					reject(e);
				}
			);
		});
		resolve(res);
	});
};

// allSettled
Promise.allSettled = function (pArr) {
	// 记录每个promise的状态
	return new Promise((resolve, reject) => {
		if (!pArr.length) return resolve([]);
		const res = [];
		pArr.forEach((pItem, index) => {
			// if (pItem instanceof Promise) {
			pItem.then(
				(v) => {
					res[index] = { status: "resolve", value: v };
				},
				(e) => {
					res[index] = { status: "reject", reason: e };
				}
			);
			// } else {
			//   res[index] = { status: "resolve", value: pItem };
			// }
		});
		resolve(res);
	});
};

同步函数 promise 化

const promisify =
	(func) =>
	(...args) =>
		new Promise((resolve, reject) => {
			args.push(function (err, value) {
				if (err) reject(err);
				else resolve(value);
			});
			func.apply(null, args);
		});

面试题

const p1 = new Promise(function (resolve, reject) {
	// ...
});

const p2 = new Promise(function (resolve, reject) {
	// ...
	resolve(p1);
});
//p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。

const p1 = new Promise(function (resolve, reject) {
	setTimeout(() => reject(new Error("fail")), 3000);
});

const p2 = new Promise(function (resolve, reject) {
	setTimeout(() => resolve(p1), 1000);
});

p2.then((result) => console.log(result)).catch((error) => console.log(error));
// Error: fail
// 上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。

实现符合A+规范的Promise
http://yoursite.com/2022/02/24/Promise/
作者
tatekii
发布于
2022年2月24日
许可协议