深入理解Promise

Tags
Published
Author
这次来搞一篇 promise 规范和基于规范手撕 Promise 的文章

什么是 Promise

Promise 表示一个异步操作的最终结果,它有三种状态,分别是:
  1. 等待态(pending)
  1. 执行态(resolved)
  1. 拒绝态(rejected)
其初始状态为 pending,在整个异步过程中,他只能处于两种状态,且一旦状态变更为另一种就不再变化
如 pending-> resolved / pending->rejected
基本过程:
  1. 初始化 Promise 状态(pending)
  1. 立即执行 Promise 中传入的 fn 函数,将Promise 内部 resolve、reject 函数作为参数传递给 fn ,按事件机制时机处理
  1. 执行 then(..) 注册回调处理数组(then 方法可被同一个 promise 调用多次)
  1. Promise里的关键是要保证,then方法传入的参数 onFulfilled 和 onRejected,必须在then方法被调用的那一轮事件循环之后的新执行栈中执行。
new Promise((resolve, reject) => { resolve("success"); //调用resolve函数使得Promise状态变更为resolved reject("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 方法可以访问其当前值终值拒因,接受两个参数onFulfilledonRejected,依据 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出去,此时thencatch才会进行捕获。
而现在的代码,promise内部如果有异步代码执行后才resolvethen不会等待异步代码执行完毕会直接执行,所以此时状态是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,需要注意的几个点:
  1. 保存之前 promise 实例的引用,即保存this
  1. 根据then回调函数执行的返回值
  • 如果是 promise 实例,那么返回的下一个 promise 实例会等待这个 promise 状态发生变化
  • 如果不是 promise 实例,根据目前情况直接执行resolvereject
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) } // 保存this const 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); // 不同点:此时是reject result 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返回的是一个新的Promise return 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对象了,那么参数有两种情况:
  1. Promise实例
  1. 不是Promise实例
static resolve(value) { if (value instanceof Promise) { // 如果是Promise实例,直接返回 return value; } else { // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED return 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 状态才成功,需要注意的点是:
  1. 所有的 promise 状态变为FULFILLED,返回的 promise 状态才变为FULFILLED
  1. 一个 promise 状态变为REJECTED,返回的 promise 状态就变为REJECTED
  1. 数组成员不一定都是 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和reject exector(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); }; // 保存this const 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); // 不同点:此时是reject result 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传递给后面的then reason => Promise.resolve(callback()).then(() => { throw reason }) //reject同理 ) } static resolve(value) { if (value instanceof Promise) { // 如果是Promise实例,直接返回 return value; } else { // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED return 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 编译成了什么样子