重学Promise,基于A+规范实现它

Promise解决了什么问题

Promise出现之前,存在嵌套关系的多个异步操作往往是下面这样的:通过层层回调(回调地狱)来满足这种嵌套关系。真实环境的代码在每次异步操作前肯定还存在很多的逻辑处理,随着代码量的增加,可维护性会越来越差。

const fs = require('fs');

fs.readFile('./a.txt', 'utf8', (err, data) => {
  // ...
  fs.readFile(data, 'utf8', (err, data) => {
    // ...
    fs.readFile(data, 'utf8', (err, data) => {
      console.log(data);
      // ...
    })
  })
})
复制代码

逻辑关系:
whiteboardappdotorg20210527081924.png

有了Promise之后,我们可以把上面的代码改写成这样:

const readFIlePromise = filename => {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, 'utf8', (err, data) => {
      if (err) reject(err);
      resolve(data);
    })
  })
}

readFIlePromise('./a.txt')
  .then(data => {
    // ...
    return readFIlePromise(data);
  })
  .then(data => {
    // ...
    return readFIlePromise(data);
  })
  .then(data => {
    console.log(data);
    // ...
  })
  .catch(err => {
    console.error(err);
  })
复制代码

逻辑关系:
whiteboardappdotorg20210527083135.png

看,Promise出现以后,用链式调用的写法改写嵌套写法是不是清晰了很多


如何实现一个Promise

很多实现Promise的库,比较著名的像bluebirdES6-Promise等都是基于Promise A+规范实现的。基于Promise A+规范实现的最明显的好处就是:可以互相调用。所以学习Promise,还是建议把A+规范读一遍。

Promise A+规范

回顾一下Promise

我们先回顾一下Promise,先知道怎么用,才能知道怎么写对吧。看代码:

const p1 = new Promise((resolve, reject) => {
  console.log('创建了p1');
  resolve('p1');
});

const p2 = p1.then(data => {
  console.log('p1-data', data);
  throw new Error('错误了');
});

const p3 = p2.then(data => {
  console.log('p2-data', data);
}).catch(err => {
  console.log('p2-error', err);
})

// 创建了p1
// p1-data p1
// p2-error Error: 错误了
复制代码

单从上面的代码层面,我们可以发现Promise具有下面几个特征:

  • 实例化的过程需要接收一个函数(暂且叫executor),内部会立即执行executor这个函数;
  • executor执行成功的话就执行resolve,将成功的数据传递出去。失败/错误就会执行reject,传递出错误信息;

结合Promise A+规范,我们可以总结出Promise所具有的特征:

  • Promise只有三种状态:pendingfulfilledrejected[规范2.1]\color{blue} {[规范 2.1]}
  • new Promise的时候,需要传入一个执行器executorexecutor会立即执行;
  • executor接收两个参数,分别是resolvereject
  • 默认状态是pendingpending状态可以转为fulfilled状态或者rejected状态,状态一旦变更,就不可改变;[规范2.1.1]\color{blue}{[规范 2.1.1]}
  • fulfilled状态必须有一个value来保存成功的结果;[规范2.1.2]\color{blue}{[规范 2.1.2]}
  • rejected状态必须有一个reason来保存失败的结果;[规范2.1.3]\color{blue}{[规范 2.1.3]}
  • Promise必须要有一个then方法,then方法接收两个参数,分别是成功后的回调onFulfilled、失败后的回调onRejected[规范2.2]\color{blue}{[规范 2.2]}
  • Promise成功后,将执行then方法的成功回调onFulfilled,并且value将作为成功后回调的参数;
  • Promise失败后,将执行then方法的失败回调onRejected,并且reason将作为失败后回调的参数;

tips:

光看上面的文字一脸懵逼,很正常,我当时也这样。可以直接看下面的实现,根据实现再去对照Promise A+规范更容易理解:

1. 先实现一个基础版的Promise

基于上面的特征,先慢慢来,我们先去实现一个基础版的Promise

// 先定义三种状态:pending、fulfilled、rejected
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class Promise {
  /**
   * 执行器
   * @param {Function} executor 
   */
  constructor(executor) {
    this.status = PENDING;          // 初始化是pending状态
    this.value = undefined;         // 保存成功的数据
    this.reason = undefined;        // 保存失败的原因

    // 成功状态执行该函数
    const resolve = data => {
        if(this.status === PENDING) {
            this.status = FULFILLED;
            this.value = data;
        }
    }

    // 失败状态执行该函数
    const reject = reason => {
        if(this.status === PENDING) {
            this.status = REJECTED;
            this.reason = reason;
        }
    }

    try {
      // new 的时候立即执行executor
      executor(resolve, reject);
    } catch(err) {
      // 对于执行器执行过程中抛出的错误,我们也用reject抛出
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    if(this.status === FULFILLED) {
      onFulfilled(this.value);
    };
    
    if(this.status === REJECTED) {
      onRejected(this.reason);
    }
  }
}
复制代码

测试Demo 1

new Promise((resolve, reject) => {
  resolve(1);
})
  .then(data => {
    console.log('success', data);
  }, err => {
    console.log('err', err);
  })

// success 1
复制代码

基础版的Promise已经完成,但是它只支持最基础的功能-传递普通值(成功/失败)。

测试Demo 2

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000)
})
  .then(data => {
    console.log('success', data);
  }, err => {
    console.log('err', err);
  })

// 什么都没有打印
复制代码

Demo 2之所以什么都没有打印,是因为new Promise传入的executor函数过了1秒才变成成功状态,而then方法在executor的同步代码执行完了以后就会立即执行(此时Promise状态还是pending,所以then的两个回调都不会执行)。

2. 升级一下基础版Promise

// 先定义三种状态:pending、fulfilled、rejected
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class Promise {
  /**
   * 执行器
   * @param {Function} executor 
   */
  constructor(executor) {
    this.status = PENDING;          // 初始化是pending状态
    this.value = undefined;         // 保存成功的数据
    this.reason = undefined;        // 保存失败的原因

    // +
    this.onFulfilledCallbacks = []  // 保存成功状态的回调队列
    this.onRejectedCallbacks = []   // 保存失败状态的回调队列

    // 成功状态执行该函数
    const resolve = data => {
      if(this.status === PENDING) {
          this.status = FULFILLED;
          this.value = data;

          // +
          this.onFulfilledCallbacks.forEach(cb => cb());
      }
    }

    // 失败状态执行该函数
    const reject = reason => {
      if(this.status === PENDING) {
          this.status = REJECTED;
          this.reason = reason;

          // +
          this.onRejectedCallbacks.forEach(cb => cb());
      }
    }

    try {
      // new 的时候立即执行executor
      executor(resolve, reject);
    } catch(err) {
      // 对于执行器执行过程中抛出的错误,我们也用reject抛出
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    if(this.status === FULFILLED) {
      onFulfilled(this.value);
    };
    
    if(this.status === REJECTED) {
      onRejected(this.reason);
    }

    // +
    if(this.status === PENDING) {
      this.onFulfilledCallbacks.push(() => {
        onFulfilled(this.value);
      });
      this.onRejectedCallbacks.push(() => {
        onRejected(this.reason);
      })
    }
  }
}
复制代码

测试Demo 3

new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(2);
  }, 1000)
})
  .then(data => {
    console.log('success', data);
  }, err => {
    console.log('err', err);
  })
// 1s以后打印出: err 2
复制代码

3. 给Promise加上值穿透和链式调用

new Promise((resolve, reject) => {
  resolve(3);
})
  .then()
  .then()
  .then(data => {
    console.log('success', data);
  }, err => {
    console.log('err', err);
  })
复制代码

我们目前实现的Promise还不支持上面这种形式的调用(即值穿透)。并且还需要根据A + 规范2.22.3来完善我们的Promise。所以建议对着A +规范看下面的实现过程:

test.js

// 先定义三种状态:pending、fulfilled、rejected
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

// 2.3
const resolvePromise = (promise, x, resolve, reject) => {
  // 2.3.1 如果promise和x是同一个,则reject,失败的原因是一个类型错误
  if (promise === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
  }
  // 2.3.3.3.3 避免多次调用,状态变更后(resolve/reject),后面就不再执行
  let called = false;
  // 2.3.3 如果x是一个对象/函数
  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    try {
      // 2.3.3.1 定义一个then变量保存x.then
      const then = x.then;
      // 2.3.3.3 如果then是一个函数
      if (typeof then === 'function') {
        // 2.3.3.3 则调用then方法,x作为this,第一个参数是成功状态的回调,第二个参数是失败状态的回调
        then.call(x, y => {
          if(called) return;
          called = true;
          // 2.3.3.3.1 执行成功回调的话,则继续执行resolvePromise
          resolvePromise(promise, y, resolve, reject);
        }, r => {
          if(called) return;
          called = true;
          // 2.3.3.3.2 执行失败状态的回调的话,则执行reject
          reject(r);
        });
      } else {
        // 2.3.3.4 如果then不是一个函数,则resolve,参数是x
        resolve(x);
      }
    } catch(e) {
      if(called) return;
      called = true;
      // 2.3.3.2 如果获取x.then属性的时候报错,则reject,错误原因是catch到的错误
      reject(e);
    }
  } else {
    // 2.3.4 如果x不是一个对象/函数,则resolve,参数是x
    resolve(x);
  }
}

class Promise {
  /**
   * 执行器
   * @param {Function} executor 
   */
  constructor(executor) {
    this.status = PENDING;          // 初始化是pending状态
    this.value = undefined;         // 保存成功的数据
    this.reason = undefined;        // 保存失败的原因
    this.onFulfilledCallbacks = []  // 保存成功状态的回调队列
    this.onRejectedCallbacks = []   // 保存失败状态的回调队列

    // 成功状态执行该函数
    const resolve = data => {
        if(this.status === PENDING) {
            this.status = FULFILLED;
            this.value = data;
            this.onFulfilledCallbacks.forEach(cb => cb());
        }
    }

    // 失败状态执行该函数
    const reject = reason => {
        if(this.status === PENDING) {
            this.status = REJECTED;
            this.reason = reason;
            this.onRejectedCallbacks.forEach(cb => cb());
        }
    }

    try {
      // new 的时候立即执行executor
      executor(resolve, reject);
    } catch (err) {
      // 对于执行器执行过程中抛出的错误,我们也用reject抛出
      reject(err);
    }
  }

  // 2.2
  then (onFulfilled, onRejected) {
    // 值穿透
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };

    let promise = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 2.2.4 onFulfilled/onRejected放到执行上下文堆栈的最后执行
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            // 2.2.7.1 onFulfilled/onRejected有返回值的话,就调用resolvePromise
            resolvePromise(promise, x, resolve, reject);
          } catch (e) {
            // 2.2.7.2 onFulfilled/onRejected抛出错误的话,就执行reject,错误原因是就是catch捕获的错误
            reject(e);
          }
        }, 0);
      };

      if (this.status === REJECTED) {
        // 2.2.4 onFulfilled/onRejected放到执行上下文堆栈的最后执行
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            // 2.2.7.1 onFulfilled/onRejected有返回值的话,就调用resolvePromise
            resolvePromise(promise, x, resolve, reject);
          } catch (e) {
            // 2.2.7.2 onFulfilled/onRejected抛出错误的话,就执行reject,错误原因是就是catch捕获的错误
            reject(e)
          }
        }, 0);
      }

      if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(() => {
          // 2.2.4 onFulfilled/onRejected放到执行上下文堆栈的最后执行
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              // 2.2.7.1 onFulfilled/onRejected有返回值的话,就调用resolvePromise
              resolvePromise(promise, x, resolve, reject);
            } catch (e) {
              // 2.2.7.2 onFulfilled/onRejected抛出错误的话,就执行reject,错误原因是就是catch捕获的错误
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          // 2.2.4 onFulfilled/onRejected放到执行上下文堆栈的最后执行
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              // 2.2.7.1 onFulfilled/onRejected有返回值的话,就调用resolvePromise
              resolvePromise(promise, x, resolve, reject);
            } catch (e) {
              // 2.2.7.2 onFulfilled/onRejected抛出错误的话,就执行reject,错误原因是就是catch捕获的错误
              reject(e)
            }
          }, 0);
        })
      }
    });

    // 2.2.7 then方法必须返回一个promise
    return promise;
  }
}
复制代码

写完需要使用Promise A+提供的测试脚本验证我们的Promise,使用方法:

  • 安装测试脚本:npm install -g promises-aplus-tests
  • 测试文件末尾加入如下代码,并导出:
Promise.defer = Promise.deferred = function () {
  let dfd = {};
  dfd.promise = new Promise((resolve,reject)=>{
      dfd.resolve = resolve;
      dfd.reject = reject;
  })
  return dfd;
}

module.exports = Promise;
复制代码
  • 最后,执行测试命令:promises-aplus-tests test.js
  • 测试结果:

image.png


实现Promise.prototype.catch()

catch方法实际上就是借助then方法实现的,只不过成功的回调传的null

Promise.prototype.catch = function (errCallBack){
    return this.then(null, errCallBack)
}

// test
new Promise((resolve, reject) => {
  reject(2);
})
  .then(data => {
    console.log('success', data);
  })
  .catch(err => {
    console.log('catch', err);    // catch 2
  })
复制代码

实现Promise.resolve()

Promise.resolve是产生一个成功的promise

Promise.resolve = function(data) {
    return new Promise((resolve, reject) => {
      resolve(data);
    })
}

// test
Promise.resolve(new Promise((resolve, reject) => {
    resolve('ok');
})).then(data=>{
  console.log(data,'success')   // ok success
}).catch(err=>{
  console.log(err,'error')
})
复制代码

Promise.resolve还需要等待功能。即如果data是一个promise,需要data执行完毕。改写constructorresolve方法:

// 成功状态执行该函数
const resolve = data => {
  // 新增:如果data是Promise,那进行递归解析它
  if (data instanceof Promise) {
    return data.then(resolve, reject);
  }
  this.status = FULFILLED;
  this.value = data;
  this.onFulfilledCallbacks.forEach(cb => cb());
}

// test
Promise.resolve(new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('ok');
  }, 3000);
})).then(data=>{
  console.log(data,'success')   // 3秒后打印:ok success
}).catch(err=>{
  console.log(err,'error')
})
复制代码

这样,Promise.resolve就具备了等待功能.


实现Promise.reject()

该方法是返回一个失败的promise,直接将错误的结果当成reason抛出即可。

Promise.reject = function(reason) {
    return new Promise((resolve, reject) => {
      reject(reason);
    })
}

// test
Promise.reject(2).then(data=>{
  console.log(data,'success')
}).catch(err=>{
  console.log(err,'error')      // 2 error
})
复制代码

实现Promise.prototype.finally()

该方法表示不管成功/失败都会执行(并不是最后执行)。如果上一次返回的成功的结果,finally执行完内部的代码以后就会把上一次成功的结果返回。如果上个promise是失败,则会把失败的原因抛出。

Promise.prototype.finally = function(callBack) {
    return this.then(data => {
      return Promise.resolve(callBack()).then(() => data);
    }, reason => {
      return Promise.resolve(callBack()).then(() => {
        throw reason;
      })
    })
}

// test
Promise.resolve(1).finally(()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(() => {
        resolve(2)
    }, 2000);
  })
}).then(data=>{
  console.log(data,'success')     // 2秒后打印:1 success
}).catch(err=>{
  console.log(err,'error')
})
复制代码

实现Promise.race()

该方法用来处理多个请求,哪个请求先执行完就返回它的结果。

Promise.race = function(promiseList) {
    if (!Array.isArray(promiseList)) {
      throw new TypeError('You must pass array')
    }
    return new Promise((resolve, reject) => {
      for (let i = 0, len = promiseList.length; i < len; i++) {
        const val = promiseList[i];
        if (val && typeof val.then === 'function') {
          val.then(resolve, reject);
        } else {  // 普通值
          resolve(val);
        }
      }
    })
  }

// test
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('ok1');
  }, 3000);
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('ok2');
  }, 2000);
})

Promise.race([1, 2, 3, p1, p2]).then(data => {
  console.log('success', data);
}, err => {
  console.log('error', err);
})
// success 1
复制代码

实现Pomise.all()

该方法用于处理多个请求,如果多个请求都成功,则返回一个数组,里面每一项是各个请求成功的结果。只要有一个失败,则失败。

Promise.all = funcion(promiseList) {
  if (!Array.isArray(promiseList)) {
    throw new TypeError('You must pass array')
  }
  return new Promise((resolve, reject) => {
    const resultArr = [];
    const len = promiseList.length;
    let currentIndex = 0;
    const getResult = (key, val) => {
      resultArr[key] = val;
      if (++currentIndex === len) {
        resolve(resultArr);
      }
    }
    for (let i = 0; i < len; i++) {
      const val = promiseList[i];
      if (val && typeof val.then === 'function') {
        val.then(data => {
          getResult(i, data);
        }, reject);
      } else {  // 普通值
        getResult(i, val);
      }
    }
  })
}
    
// test
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('ok1');
  }, 1000);
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('ok2');
  }, 2000);
})

Promise.all([1,2,3,p1,p2]).then(data => {
  console.log('success', data);
}, err => {
  console.log('error', err);
})
// 2秒后打印:success [ 1, 2, 3, 'ok1', 'ok2' ]
复制代码

实现Promise.allSettled()

该方法会等到所有参数实例都返回结果,不管是reject还是resolve。最后会返回一个数组,每一项都包含状态和值两个属性。

Promise.allSettled = function(promiseList) {
    if (!Array.isArray(promiseList)) {
      throw new TypeError('You must pass array')
    }
    return new Promise((resolve, reject) => {
      const resultArr = [];
      const len = promiseList.length;
      let currentIndex = 0;
      const getResult = (key, val, status) => {
        resultArr[key] = {
          status: status,
        };
        resultArr[key].status === 'fulfilled' ? resultArr[key].value = val : resultArr[key].reason = val;
        if (++currentIndex === len) {
          resolve(resultArr);
        }
      }
      for(let i = 0; i < len; i++) {
        const val = promiseList[i];
        if (val && typeof val.then === 'function') {
          val.then(data => {
            getResult(i, data, 'fulfilled');
          }, reason => {
            getResult(i, reason, 'rejected');
          })
        } else {
          getResult(i, val, 'fulfilled');
        }
      }
    })
  }

// test
const promise1 = Promise.resolve('ok1');
const promise2 = Promise.reject('err1');
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('ok2');
  }, 1000);
})
const allSettledPromise = Promise.allSettled([promise1, promise2, promise3]);

allSettledPromise.then(function (results) {
  console.log(results);
});
// 1秒后输出
/*
[
  { status: 'fulfilled', value: 'ok1' },
  { status: 'rejected', reason: 'err1' },
  { status: 'fulfilled', value: 'ok2' }
]
*/
复制代码

实现Promise.any()

该方法和Promise.all的情况相反,只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

Promise.any = function(promiseList) {
    if (!Array.isArray(promiseList)) {
      throw new TypeError('You must pass array')
    }
    return new Promise((resolve, reject) => {
      const resultArr = [];
      const len = promiseList.length;
      let currentIndex = 0;
      const getResult = (key, val) => {
        resultArr[key] = val;
        if (++currentIndex === len) {
          reject(resultArr);
        }
      }
      for (let i = 0; i < len; i++) {
        const val = promiseList[i];
        if (val && typeof val.then === 'function') {
          val.then(resolve, reason => {
            getResult(i, reason);
          });
        } else {  // 普通值
          resolve(val);
        }
      }
    })
  }

// test
const promise1 = Promise.reject('err1');
const promise2 = Promise.reject('err2');
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('err3');
  }, 1000);
})
const allSettledPromise = Promise.any([promise1, promise2, promise3]);

allSettledPromise
  .then(data => {
    console.log('resolve', data);
  })
  .catch(reason => {
    console.log('reject', reason);
  })
// 1秒后输出:reject [ 'err1', 'err2', 'err3' ]
复制代码

至此,ES Promise的方法都实现完了。自己实现完一遍以后,对Promisethen的链式调用以及值穿透的理解会更加透彻。

参考链接

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享