这次来搞一篇 promise 规范和基于规范手撕 Promise 的文章
什么是 Promise
Promise 表示一个异步操作的最终结果,它有三种状态,分别是:
- 等待态(pending)
- 执行态(resolved)
- 拒绝态(rejected)
其初始状态为 pending,在整个异步过程中,他只能处于两种状态,且一旦状态变更为另一种就不再变化。
如 pending-> resolved / pending->rejected
基本过程:
- 初始化 Promise 状态(pending)
- 立即执行 Promise 中传入的 fn 函数,将Promise 内部 resolve、reject 函数作为参数传递给 fn ,按事件机制时机处理
- 执行 then(..) 注册回调处理数组(then 方法可被同一个 promise 调用多次)
- Promise里的关键是要保证,then方法传入的参数 onFulfilled 和 onRejected,必须在then方法被调用的那一轮事件循环之后的新执行栈中执行。
new Promise((resolve, reject) => {resolve("success"); //调用resolve函数使得Promise状态变更为resolvedreject("fail"); //此处无效 (已经变为 resolved了)});
需要注意的是:在使用 new 构造 Promise对象 的时候,构造函数内部的代码会立即执行 。
new Promise((resolve, reject) => {console.log("first");});console.log("second");//first//second
上面这段函数执行的结果先打印first 后打印second
简而言之,Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是 then 方法,该方法注册了两个回调函数,用于接收 promise 的终值或本 promise 不能执行的原因。
Promise A+ 规范
Promise 用法
new Promise((resolve,reject)=>{//resolve([value]) or reject([error])}).then(res=>{},err=>{}).then(res=>{},err=>{}).catch(err => console.log(err))Promise.resolve();Promise.reject();Promise.all([promise1,promise2,...]).then();Promise.race([promise1,promise2,...]).then();
Promise的构造方法接收一个executor(),在new Promise()时就立刻执行这个 executor 回调executor()内部的异步任务被放入宏/微任务队列,等待执行then()被执行,收集成功/失败回调,放入成功/失败队列executor()的异步任务被执行,触发resolve/reject,从成功/失败队列中取出回调依次执行- 状态改变后触发 then、catch 的回调
- Promise 拥有静态方法 resolve、reject、all、race
仔细一看,这不就是观察者模式:then 收集依赖 -> 异步触发 resolve -> resolve 执行依赖
根据以上写出如下框架代码
class Promise {// 构造方法接收一个回调constructor(exector) {}then() {}catch() {}static resolve() {}static reject() {}static race() {}static all() {}}
首先定义三种状态
然后在构造函数中执行 exector()
const PENDING = "PENDING"; // 进行中const FULFILLED = "FULFILLED"; // 已成功const REJECTED = "REJECTED"; // 已失败class Promise {constructor(exector) {this.status = PENDING; //初始化状态this.value = undefined; //准备值和错误结果this.error = undefined;const resolve = (value) => {if (this.status === PENDING) {this.status = FULFILLED;this.value = value;}};const reject = (error) => {if (this.status === PENDING) {this.status = REJECTED;this.value = error;}};exector(resolve, reject);// 立即执行exector// 把内部的resolve和reject方法传入executor,这样使用者可以调用resolve和reject方法}}
接下来实现 then 方法,then 方法可以访问其当前值、终值和拒因,接受两个参数onFulfilled 和 onRejected,依据 Promise A+规范实现相关特性:
const status = {pending: "PENDING",fulfiled: "FULFILED",rejected: "REJECTED",};class Promise {constructor(exector) {this.status = PENDING; //初始化状态this.value = undefined; //准备值和错误结果this.error = undefined;const resolve = (value) => {if (this.status === PENDING) {this.status = FULFILLED;this.value = value;}};const reject = (error) => {if (this.status === PENDING) {this.status = REJECTED;this.value = error;}};try {exector(resolve, reject);} catch (e) {reject(e);}// 立即执行exector// 把内部的resolve和reject方法传入executor,这样使用者可以调用resolve和reject方法}//then可以捕获exector的错误和结果,访问当前值,终值和拒因 所以需要try catch exector执行的错误then(onFulfilled, onRejected) {// then是微任务,这里用setTimeout模拟setTimeout(() => {if (this.status === FULFILLED) {onFulfilled(this.value);} else if (this.status === REJECTED) {onRejected(this.error);}});}}
测试一下:
const promise = new Promise((resolve, reject) => {console.log("1");setTimeout(() => {console.log("4");});Math.random() < 0.5 ? resolve(3) : reject(-3);console.log("1.5");}).then((res) => console.log(res),(err) => console.log(err));console.log("2");//有结果 1 1.5 2 4 3/-3 异步调用问题console.log(promise); //undefined 还需解决链式调用问题
then参数期望是函数,传入非函数则会发生值穿透。值传透可以理解为,当传入 then 的不是函数的时候,这个 then 是无效的。
原理上是当 then 中传入的不算函数,则这个promise返回上一个promise的值,这就是发生值穿透的原因,所以只需要对then的两个参数进行设置就行了:
onFulfilled =typeof onFulfilled === "function" ? onFulfilled : (value) => value;onRejected =typeof onRejected === "function"? onRejected: (error) => {throw new Error(error instanceof Error ? error.message : error);};
开发中经常会将接口放于promise内部,等接口请求响应成功把数据resolve出去,或失败时把数据reject出去,此时then、catch才会进行捕获。
而现在的代码,promise内部如果有异步代码执行后才resolve,then不会等待异步代码执行完毕会直接执行,所以此时状态是PENDING,不会触发then的回调函数。
在constructor中新增_resolveQueue、_rejectQueue维护成功态、失败态任务队列
// 成功态回调函数队列this._resolveQueue = [];// 失败态回调函数队列this._rejectQueue = [];const resolve = (value) => {// 只有进行中状态才能更改状态if (this.status === PENDING) {this.status = FULFILLED;this.value = value;// 新增代码:// 成功态函数依次执行this._resolveQueue.length > 0 && this._resolveQueue.forEach((fn) => fn(this.value));//while(this._resolveQueue.length > 0){// const callback = this._resolveQueue.shift();// callback(this.value)//}}};const reject = (error) => {// 只有进行中状态才能更改状态if (this.status === PENDING) {this.status = REJECTED;this.error = error;// 新增代码:// 失败态函数依次执行this._rejectQueue.length > 0 this._rejectQueue.forEach((fn) => fn(this.error));//while(this._rejectQueue.length > 0){// const callback = this._rejectQueue.shift();// callback(this.value)//}}};then(onFulfilled, onRejected) {// then是微任务,这里用setTimeout模拟setTimeout(() => {// 新增代码:if (this.status === PENDING) {// 状态是PENDING下执行// 说明promise内部有异步代码执行,还未改变状态,添加到成功/失败回调任务队列即可this._resolveQueue.push(onFulfilled);this._rejectQueue.push(onRejected);}else if (this.status === FULFILLED) {// FULFILLED状态下才执行onFulfilled(this.value);} else if (this.status === REJECTED) {// REJECTED状态下才执行onRejected(this.error);}})
Promise的一大优势就是支持链式调用,具体来说就是then方法的具体实现,实际上是返回了一个Promise,需要注意的几个点:
- 保存之前 promise 实例的引用,即保存
this - 根据
then回调函数执行的返回值
- 如果是 promise 实例,那么返回的下一个 promise 实例会等待这个 promise 状态发生变化
- 如果不是 promise 实例,根据目前情况直接执行
resolve或reject
then(onFulfilled, onRejected) {onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;onRejected = typeof onRejected === 'function'? onRejected:error => { throw new Error(error instanceof Error ? error.message:error) }// 保存thisconst self = this;return new Promise((resolve, reject) => {if (self.status === PENDING) {//注意这个this指向目前的Promise对象,而不是新的Promise//再强调一遍,很重要://目前的Promise(不是这里return的新Promise)的resolve和reject函数其实一个作为微任务//因此他们不是立即执行,而是等then调用完成后执行(this.) self._resolveQueue.push(() => {// try捕获错误try {// 模拟微任务setTimeout(() => {const result = onFulfilled(self.value);// 分两种情况:// 1. 回调函数返回值是Promise,执行then操作// 2. 如果不是Promise,调用新Promise的resolve函数//下面执行之后的下一步,也就是记录执行的状态,决定新Promise如何表现//如果返回值x是一个Promise对象,就执行then操作//如果不是Promise,直接调用新Promise的resolve函数,//新Promise的fulfilAry现在为空,在新Promise的then操作后.新Promise的resolve执行result instanceof Promise ? result.then(resolve, reject) : resolve(result);})} catch(e) {reject(e);}});self._rejectQueue.push(() => {// 以下同理try {setTimeout(() => {const result = onRejected(self.error);// 不同点:此时是rejectresult instanceof Promise ? result.then(resolve, reject) : resolve(result);})} catch(e) {reject(e);}})} else if (self.status === FULFILLED) {try {setTimeout(() => {const result = onFulfilled(self.value);result instanceof Promise ? result.then(resolve, reject) : resolve(result);});} catch(e) {reject(e);}} else if (self.status === REJECTED){try {setTimeout(() => {const result = onRejected(self.error);result instanceof Promise ? result.then(resolve, reject) : resolve(result);})} catch(e) {reject(e);}}});}
测试:
let p1 = new Promise((resolve, reject) => {setTimeout(() => {Math.random() < 0.5 ? resolve(100) : reject(-100);}, 1000);});let p2 = p1.then((result) => {//执行then返回的是一个新的Promisereturn result + 100;});let p3 = p2.then((result) => {console.log(result);},(reason) => {console.log(reason);});
catch()方法
Promise.prototype.catch就是Promise.prototype.then(null, onRejected)的别名,所以实现就很简单了:
catch(onRejected) {return this.then(null, onRejected);}
resolve()
这里就不考虑参数是thenable对象了,那么参数有两种情况:
Promise实例- 不是
Promise实例
static resolve(value) {if (value instanceof Promise) {// 如果是Promise实例,直接返回return value;} else {// 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLEDreturn new Promise((resolve, reject) => resolve(value));}}
reject()
Promise.reject也会返回一个 Promise 实例,状态为REJECTED。
与Promise.resolve不同的是,Promise.reject方法的参数会原封不动地作为reject的参数
static reject(error) {return new Promise((resolve, reject) => {reject(error);})}
all()
返回一个 promise 对象,只有当所有 promise 都成功时返回的 promise 状态才成功,需要注意的点是:
- 所有的 promise 状态变为
FULFILLED,返回的 promise 状态才变为FULFILLED。 - 一个 promise 状态变为
REJECTED,返回的 promise 状态就变为REJECTED。 - 数组成员不一定都是 promise,需要使用
Promise.resolve()处理。
static all(promiseArr) {let result = []// 记录已经成功执行的promise个数let count = 0;return new Promise((resolve, reject) => {promiseArr.forEach((p, i) => {// Promise.resolve()处理,确保每一个都是promise实例Promise.resolve(p).then(val => {result[i] = val;count++;// 如果全部执行完,返回promise的状态就可以改变了if (count === promiseArr.length) resolve(result);},err => reject(err), //只要有一个Promise被reject时,MyPromise的状态变为reject);}})}
race()
static race(promiseArr) {return new Promise((resolve, reject) => {promiseArr.forEach(p => {Promise.resolve(p).then(val => resolve(val),err => reject(err),)})})}
完整代码
// 模拟实现Promise// Promise利用三大手段解决回调地狱:// 1. 回调函数延迟绑定// 2. 返回值穿透// 3. 错误冒泡// 定义三种状态const PENDING = "PENDING"; // 进行中const FULFILLED = "FULFILLED"; // 已成功const REJECTED = "REJECTED"; // 已失败class Promise {constructor(exector) {if (typeof executor !== "function") {// 非标准 但与Chrome谷歌保持一致throw TypeError("Promise resolver " + executor + " is not a function");}// 初始化状态this.status = PENDING;// 将成功、失败结果放在this上,便于then、catch访问this.value = undefined;this.error = undefined;// 成功态回调函数队列this._resolveQueue = [];// 失败态回调函数队列this._rejectQueue = [];const resolve = (value) => {// 只有进行中状态才能更改状态if (this.status === PENDING) {this.status = FULFILLED;this.value = value;// 成功态函数依次执行this._resolveQueue.length > 0 &&this._resolveQueue.forEach((fn) => fn(this.value));}};const reject = (error) => {// 只有进行中状态才能更改状态if (this.status === PENDING) {this.status = REJECTED;this.error = error;// 失败态函数依次执行this._rejectQueue.length > 0 && this._rejectQueue.forEach((fn) => fn(this.error));}};try {// 立即执行executor// 把内部的resolve和reject传入executor,用户可调用resolve和rejectexector(resolve, reject);} catch (e) {// executor执行出错,将错误内容reject抛出去reject(e);}}then(onFulfilled, onRejected) {onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;onRejected = typeof onRejected === "function"? onRejected: (error) => {throw new Error(error instanceof Error ? error.message : error);};// 保存thisconst self = this;return new Promise((resolve, reject) => {if (self.status === PENDING) {self._resolveQueue.push(() => {// try捕获错误try {// 模拟微任务setTimeout(() => {const result = onFulfilled(self.value);// 分两种情况:// 1. 回调函数返回值是Promise,执行then操作// 2. 如果不是Promise,调用新Promise的resolve函数result instanceof Promise? result.then(resolve, reject): resolve(result);});} catch (e) {reject(e);}});self._rejectQueue.push(() => {// 以下同理try {setTimeout(() => {const result = onRejected(self.error);// 不同点:此时是rejectresult instanceof Promise? result.then(resolve, reject): resolve(result);});} catch (e) {reject(e);}});} else if (self.status === FULFILLED) {try {setTimeout(() => {const result = onFulfilled(self.value);result instanceof Promise? result.then(resolve, reject): resolve(result);});} catch (e) {reject(e);}} else if (self.status === REJECTED) {try {setTimeout(() => {const result = onRejected(self.error);result instanceof Promise? result.then(resolve, reject): resolve(result);});} catch (e) {reject(e);}}});}catch(onRejected) {return this.then(null, onRejected);}//finally方法finally(callback) {return this.then(value => Promise.resolve(callback()).then(() => value), //执行回调,并returnvalue传递给后面的thenreason => Promise.resolve(callback()).then(() => { throw reason }) //reject同理)}static resolve(value) {if (value instanceof Promise) {// 如果是Promise实例,直接返回return value;} else {// 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLEDreturn new Promise((resolve, reject) => resolve(value));}}static reject(error) {return new Promise((resolve, reject) => {reject(error);});}static all(promiseArr) {let result = []// 记录已经成功执行的promise个数let count = 0;return new Promise((resolve, reject) => {promiseArr.forEach((p, i) => {// Promise.resolve()处理,确保每一个都是promise实例Promise.resolve(p).then(val => {result[i] = val;count++;// 如果全部执行完,返回promise的状态就可以改变了if (count === promiseArr.length) resolve(result);},err => reject(err), //只要有一个Promise被reject时,MyPromise的状态变为reject);}})}static race(promiseArr) {return new Promise((resolve, reject) => {promiseArr.forEach((p) => {Promise.resolve(p).then((val) => resolve(val),(err) => reject(err));});});}}
9k 字 | Promise/async/Generator 实现原理解析
async/await 原理及执行顺序分析
ES6 系列之 Babel 将 Async 编译成了什么样子
