根据Promises/A+规范手写promise

基本概念

Promise是异步编程的一种解决方案,比传统的解决方案------回调函数和事件------更合理和更强大。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,同时promise必须遵守Promises/A+规范。

基本术语

  1. promise是带有then方法的对象或函数,其行为符合此规范。

  2. thenable是定义then方法的对象或函数。

  3. value是任何合法的JavaScript值(包括undefined、thenable或promise)。

  4. exception是使用throw语句抛出的值。

  5. reason是一个值,表示promise被拒绝的原因。

Promise状态(Promise States)

  • 根据Promises/A+规范原文描述,promise的状态有且只有三种: pending(等待态)fulfilled(成功态)rejected(失败态)

    • pending态时,可以改变其状态为fulfilledrejected
    • 其中fulfilledrejected时状态是不可被改变的,这意味着状态是不可逆的。
  • promise处于pending态时,能够改变promise状态的方法有三种:

    1. 调用resolve把状态改为fulfilled;
    2. 调用reject把状态改为rejected;
    3. 抛出错误thow new Error(),把状态改为rejected;

直接冻手

  • 在代码中,会尽量给家人们标记好代码在Promises/A+的具体说明位置,方便结合规范食用。

第一步

  • 定义promise的三种状态
  • 定义成功原因value
  • 定义失败原因reason
  • promise在创建时,会接收一个functionexecutor作为参数,并且会默认执行一次此参数,同时返回内部定义的resolvereject方法供外部修改状态。
  • 实现一个then函数,在promise状态被修改时,需要调用对应的函数,接收两个参数:
    1. onFulfilled 成功后执行的回调。
    2. onRejected 失败后执行的回调。
js 复制代码
const PENDING = 'PENDING'; // 默认态
const FULFILLED = 'FULFILLED'; // 成功态
const REJECTED = 'REJECTED'; // 失败态

class Promise1 {
	constructor(executor) {
		this.state = PENDING; // 状态
		this.reason = null; // 失败原因
		this.value = null; // 成功数据
		const resolve = (value) => {
      // pending状态时,状态才可变更
			if (this.state === PENDING) { // 2.1.2.1 must not transition to any other state.
				this.state = FULFILLED;
				this.value = value; // 2.1.2.2 must have a value, which must not change.
			}
		};
		const reject = (reason) => {
      // pending状态时,状态才可变更
			if (this.state === PENDING) { // 2.1.3.1 must not transition to any other state.
				this.state = REJECTED;
				this.reason = reason; // 2.1.3.2 must have a reason, which must not change.
			}
		};
		// 能够改变promise状态的有resolve,reject和抛出异常throw new Error();
		try {
			executor(resolve, reject); // 默认会执行一次, 同时返回内部定义的resolve和reject方法供外部修改状态。
		} catch (error) {
      // 如果运行报错,会直接当作失败处理
			reject(error);
		}
	}
	then(onFulfilled, onRejected) {
    // 2.2 A promise must provide a then method to access its current or eventual value or reason.
		// A promise's then method accepts two arguments: onFulfilled AND onRejected
    if (this.state === FULFILLED) {
			// 当前状态为成功态调用onFulfilled
			onFulfilled(this.value);
		} else if (this.state === REJECTED) {
			// 当前状态为失败态调用onRejected
			onRejected(this.reason);
		}
	}
}
  • 我们进行下简单的测试
js 复制代码
const promise = new Promise1((resolve, reject) => {
  resolve('ok');
}).then(data => {
  console.log(data); // ok
});
const promise = new Promise1((resolve, reject) => {
  reject('fail');
}).then(data => {}, error => {
  console.log(error); // fail
});
const promise = new Promise1((resolve, reject) => {
  resolve('ok');
  reject('fail'); // 这里将不被执行
}).then(data => {
  console.log(data); // ok
}, error => {
  console.log(error); // 不打印
});

第二步

众所周知promise是解决回调地狱的,因此其使用场景的执行是异步的,第一步then被调用时,promise仍处于pending态,很显然是无法解决的。

要解决异步调用的问题,需要利用观察者模式,可以在另一篇文章中获得更详细的答案,这里就不详细赘述了。

  1. 定义onResolvedCallBacks缓存所有onFulfilled观察者函数;
  2. 定义onRejectedCallBacks缓存所有的onRejected观察者函数;
  3. then方法中增加判断pending逻辑;
  4. resolve被调用时,执行所有的onResolvedCallBacks
  5. reject被调用时,执行所有的onRejectedCallBacks
js 复制代码
class Promise1 {
	constructor(executor) {
		// 省略....
		const resolve = (value) => {
      // pending状态时,状态才可变更
			if (this.state === PENDING) { // 2.1.2.1 must not transition to any other state.
				this.state = FULFILLED;
				this.value = value; // 2.1.2.2 must have a value, which must not change.
        this.onResolvedCallBacks.forEach((fn) => fn());
			}
		};
		const reject = (reason) => {
      // pending状态时,状态才可变更
			if (this.state === PENDING) { // 2.1.3.1 must not transition to any other state.
				this.state = REJECTED;
				this.reason = reason; // 2.1.3.2 must have a reason, which must not change.
        this.onRejectedCallBacks.forEach((fn) => fn());
			}
		};
		// 省略....
	}
	then(onFulfilled, onRejected) {
    // 2.2 A promise must provide a then method to access its current or eventual value or reason.
		// A promise's then method accepts two arguments: onFulfilled AND onRejected
    if (this.state === FULFILLED) {
			// 省略....
		} else if (this.state === REJECTED) {
			// 省略....
		} else if (this.state === PENDING) {
				// 异步,状态为等待
				// 观察者模式,观察state变化,缓存onFulfilled和onRejected
				this.onResolvedCallBacks.push(() => {
					onFulfilled(this.value);
				});
				this.onRejectedCallBacks.push(() => {
					onRejected(this.reason);
				});
		}
	}
}
  • setTimeout模拟异步,再次测试
js 复制代码
const promise = new Promise1((resolve, reject) => {
  setTimeout(() => {
    resolve('ok');
  }, 1000);
}).then(data => {
  console.log(data); // ok
});
const promise = new Promise1((resolve, reject) => {
  setTimeout(() => {
    reject('fail');
  }, 1000);
}).then(data => {}, error => {
  console.log(error); // fail
});

第三步

最最最最重要的核心逻辑来了,"众所周知"它又来了,众所周知,promise是允许链式调用的。

js 复制代码
const promise = new Promise1((resolve, reject) => {
  setTimeout(() => {
    resolve('ok');
  }, 1000);
}).then(data => {
  return data;
}).then(data2 => {
  console.log(data2); // ok
});

分析

文档2.2.7说到then must return a promise, then 必须返回一个promise,也正因为返回一个promise,我们才能够不断的调用其then方法。

那么疑问来了,完成第一、二步后,我们返回的都是一个静态的值value或者reason,需要返回一个promise,能不能直接返回this呢?

答案很显然是否定的,因为promise的状态一旦改变之后是不可逆的,如果返回当前的this状态不可逆将没有任何意义,所以我们需要返回一个new Promise。

js 复制代码
class Promise1 {
	constructor(executor) {
		// 省略....
	}
	then(onFulfilled, onRejected) {
    if (this.state === FULFILLED) {
			// 省略....
		} else if (this.state === REJECTED) {
			// 省略....
		} else if (this.state === PENDING) {
				// 省略....
		}
    const promise2 = new Promise1((resolve, reject) => {
      
    });
    return promise2;
	}
}

把测试案例贴在这里对比,会比较明显看出问题。

js 复制代码
const promise = new Promise1((resolve, reject) => {
  setTimeout(() => {
    resolve('ok');
  }, 1000);
}).then(data => {
  return data; // a
});
promise.then(data2 => { // b
  console.log(data2);  // undefined
});

确实已经返回了一个新的promise,并且可以调用then方法,但在b步骤中的data2是undefined,我们要如何将a步骤的数据传递给b呢?

显而易见,data就是第一个promiseonFulfilled运行结果,官方文档2.2.7.1也有相关叙述

onFulfilledonRejected的运行结果为x,把then的运行逻辑丢进promise2里,将x返回给promise2即可。

js 复制代码
class Promise1 {
	constructor(executor) {
		// 省略....
	}
	then(onFulfilled, onRejected) {
    const promise2 = new Promise1((resolve, reject) => {
    // 2.2 A promise must provide a then method to access its current or eventual value or reason.
		// A promise's then method accepts two arguments: onFulfilled AND onRejected
    if (this.state === FULFILLED) {
        // 当前状态为成功态调用onFulfilled
        const x = onFulfilled(this.value);
      	resolve(x);
      } else if (this.state === REJECTED) {
        // 当前状态为失败态调用onRejected
        const x = onRejected(this.reason);
        reject(x);
      } else if (this.state === PENDING) {
          // 异步,状态为等待
          // 观察者模式,观察state变化,缓存onFulfilled和onRejected
          this.onResolvedCallBacks.push(() => {
            const x = onFulfilled(this.value);
            resolve(x);
          });
          this.onRejectedCallBacks.push(() => {
            const x = onRejected(this.reason);
            reject(x);
          });
      }
    });
    return promise2;
	}
}

继续阅读2.2.7.1后半句run the Promise Resolution Procedure [[Resolve]](promise2, x),运行一个promise的解析函数,意思就是要把promise2onFulfilledonRejected的运行结果用一个处理函数进行处理,处理什么呢?当promise2onFulfilledonRejected运行结果有可能还是一个promise,因此需要进行处理,并且把promise2也以参数的形式传给处理函数。

再分析

  • promise2以参数的形式传给处理函数,按照上述情况很明显是获取不到promise2实例的,再看看官方文档3.1
scss 复制代码
简而言之,通过"宏任务"机制或者"微任务"机制来确保这个异步执行,这里紧用`setTimeout`实现

```js
function resolvePromise(x, promise2, resolve, reject) {
  
}
class Promise1 {
	constructor(executor) {
		// 省略....
	}
	then(onFulfilled, onRejected) {
    const promise2 = new Promise1((resolve, reject) => {
			if (this.state === FULFILLED) {
				// 成功态调用
				// 获取fulfilled返回结果,根据返回结果判断是调用promise2的resolve还是reject
				setTimeout(() => {
					try {
						const x = onFulfilled(this.value);
						resolvePromise(x, promise2, resolve, reject);
					} catch (error) {
						reject(error);
					}
				}, 0);
			} else if (this.state === REJECTED) {
				// 失败态调用
				// 获取rejected返回结果,根据返回结果判断是调用promise2的resolve还是reject
				setTimeout(() => {
					try {
						const x = onRejected(this.reason);
						resolvePromise(x, promise2, resolve, reject);
					} catch (error) {
						reject(error);
					}
				}, 0);
			} else if (this.state === PENDING) {
				// 异步,状态为等待
				// 观察者模式,观察state变化,缓存onFulfilled和onRejected
				this.onResolvedCallBacks.push(() => {
					setTimeout(() => {
						try {
							const x = onFulfilled(this.value);
							resolvePromise(x, promise2, resolve, reject);
						} catch (error) {
							reject(error);
						}
					}, 0);
				});
				this.onRejectedCallBacks.push(() => {
					setTimeout(() => {
						try {
							const x = onRejected(this.reason);
							resolvePromise(x, promise2, resolve, reject);
						} catch (error) {
							reject(error);
						}
					}, 0);
				});
			}
		});
    return promise2;
	}
}
```
  • 接下来就剩下resolvePromise的实现了,可以在then方法中返回一个promise,看看文档2.3 The Promise Resolution Procedure,根据文档的判断逻辑,一步一步进行判断即可。
js 复制代码
function resolvePromise(x, promise2, resolve, reject) {
  // 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
  // 如果引用了自己,需要抛出一个错误。
  if (x === promise2) {
		return reject(new TypeError('循环引用promise'));
	}
  // 2.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
  let called = false;
	if ((typeof x === 'object' && x !== null) || typeof x === 'function') { // 2.3.3 Otherwise, if x is an object or function
		// 这种情况才有可能是promise
		// Let then be x.then
		try {
			let then = x.then; // 2.3.3.1 Let then be x.then
			if (typeof then === 'function') {
        // 2.3.3.3 If then is a function, call it with x as this, first argument resolvePromise, and second argument 				  rejectPromise, where
				then.call(
					x,
					(y) => { // 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
						if (called) return;
						called = true;
						resolvePromise(y, promise2, resolve, reject);
					},
					(r) => { // 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
						if (called) return;
						called = true;
						reject(r);
					}
				);
			} else {
        // 2.3.3.4 If x is not an object or function, fulfill promise with x
				// x是普通值, 直接返回
				resolve(x);
			}
		} catch (error) {
			// 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
			if (called) return;
			called = true;
			reject(error);
		}
	} else {
    // 2.3.3.4 If x is not an object or function, fulfill promise with x
		// x是普通值, 直接返回
		resolve(x);
	}
}

完整代码

js 复制代码
const PENDING = 'PENDING'; // 默认态
const FULFILLED = 'FULFILLED'; // 成功态
const REJECTED = 'REJECTED'; // 失败态

function resolvePromise(x, promise2, resolve, reject) {
  // 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
  // 如果引用了自己,需要抛出一个错误。
  if (x === promise2) {
		return reject(new TypeError('循环引用promise'));
	}
  // 2.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
  let called = false;
	if ((typeof x === 'object' && x !== null) || typeof x === 'function') { // 2.3.3 Otherwise, if x is an object or function
		// 这种情况才有可能是promise
		// Let then be x.then
		try {
			let then = x.then; // 2.3.3.1 Let then be x.then
			if (typeof then === 'function') {
        // 2.3.3.3 If then is a function, call it with x as this, first argument resolvePromise, and second argument 				  rejectPromise, where
				then.call(
					x,
					(y) => { // 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
						if (called) return;
						called = true;
						resolvePromise(y, promise2, resolve, reject);
					},
					(r) => { // 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
						if (called) return;
						called = true;
						reject(r);
					}
				);
			} else {
        // 2.3.3.4 If x is not an object or function, fulfill promise with x
				// x是普通值, 直接返回
				resolve(x);
			}
		} catch (error) {
			// 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
			if (called) return;
			called = true;
			reject(error);
		}
	} else {
    // 2.3.3.4 If x is not an object or function, fulfill promise with x
		// x是普通值, 直接返回
		resolve(x);
	}
}

class Promise1 {
	constructor(executor) {
		this.state = PENDING; // 状态
		this.reason = null; // 失败原因
		this.value = null; // 成功数据
		this.onResolvedCallBacks = []; // 成功态的回调缓存
		this.onRejectedCallBacks = []; // 失败态的回调缓存
		const resolve = (value) => {
      // pending状态时,状态才可变更
			if (this.state === PENDING) { // 2.1.2.1 must not transition to any other state.
				this.state = FULFILLED;
				this.value = value; // 2.1.2.2 must have a value, which must not change.
        this.onResolvedCallBacks.forEach((fn) => fn());
			}
		};
		const reject = (reason) => {
      // pending状态时,状态才可变更
			if (this.state === PENDING) { // 2.1.3.1 must not transition to any other state.
				this.state = REJECTED;
				this.reason = reason; // 2.1.3.2 must have a reason, which must not change.
        this.onRejectedCallBacks.forEach((fn) => fn());
			}
		};
		// 能够改变promise状态的有resolve,reject和抛出异常throw new Error();
		try {
			executor(resolve, reject); // 默认会执行
		} catch (error) {
			reject(error);
		}
	}
	then(onFulfilled, onRejected) {
		// 值的穿透
		// 2.2.1.1 If onFulfilled is not a function, it must be ignored.
		// 2.2.1.2 If onRejected is not a function, it must be ignored.
		onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
		onRejected =
			typeof onRejected === 'function'
				? onRejected
				: (e) => {
						throw e;
				  };
		const promise2 = new Promise1((resolve, reject) => {
			if (this.state === FULFILLED) {
				// 成功态调用
				// 获取fulfilled返回结果,根据返回结果判断是调用promise2的resolve还是reject
				setTimeout(() => {
					try {
						const x = onFulfilled(this.value);
						resolvePromise(x, promise2, resolve, reject);
					} catch (error) {
						reject(error);
					}
				}, 0);
			} else if (this.state === REJECTED) {
				// 失败态调用
				// 获取rejected返回结果,根据返回结果判断是调用promise2的resolve还是reject
				setTimeout(() => {
					try {
						const x = onRejected(this.reason);
						resolvePromise(x, promise2, resolve, reject);
					} catch (error) {
						reject(error);
					}
				}, 0);
			} else if (this.state === PENDING) {
				// 异步,状态为等待
				// 观察者模式,观察state变化,缓存onFulfilled和onRejected
				this.onResolvedCallBacks.push(() => {
					setTimeout(() => {
						try {
							const x = onFulfilled(this.value);
							resolvePromise(x, promise2, resolve, reject);
						} catch (error) {
							reject(error);
						}
					}, 0);
				});
				this.onRejectedCallBacks.push(() => {
					setTimeout(() => {
						try {
							const x = onRejected(this.reason);
							resolvePromise(x, promise2, resolve, reject);
						} catch (error) {
							reject(error);
						}
					}, 0);
				});
			}
		});
		return promise2;
	}
}
相关推荐
小镇程序员3 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
疯狂的沙粒3 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪3 小时前
AJAX的基本使用
前端·javascript·ajax
力透键背3 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript
程楠楠&M3 小时前
node.js第三方Express 框架
前端·javascript·node.js·express
weiabc3 小时前
学习electron
javascript·学习·electron
想自律的露西西★4 小时前
用el-scrollbar实现滚动条,拖动滚动条可以滚动,但是通过鼠标滑轮却无效
前端·javascript·css·vue.js·elementui·前端框架·html5
白墨阳4 小时前
vue3:瀑布流
前端·javascript·vue.js
uzong4 小时前
7 年 Java 后端,面试过程踩过的坑,我就不藏着了
java·后端·面试
霍先生的虚拟宇宙网络4 小时前
webp 网页如何录屏?
开发语言·前端·javascript