一、引言

这周末,忽然想起小组读书打卡还有一个撸一个Promise的工作还没有实现,尽管我之前有试过撸一个Promise,然而没试过应用PromiseA+来测试,所以打算写一篇wiki来记录一下我实现的这一个过程。

二、本人写一个Promise须要做什么

首先,咱们要明确咱们要实现一个Promise须要去做什么。

1.因为新建Promise要用到new关键字,所以咱们须要将手动实现的Promise封装成一个类;

2.因为new Promise()的参数是一个函数,所以手动实现的Promise的参数是一个函数

3.因为传进Promise外面的函数还有两个参数resolve函数和reject函数,这两个函数的作用是扭转Promise的状态;

4.promise对象有三个状态pending, fullfilled, rejected,初始状态为pending,调用resolve会将其改为fullfilled,调用reject会改为rejected,而且Promise对象的状态一旦产生扭转便不能再变为其余的状态,有且只有pending变为fullfilled和pending变为rejected两种可能

5.promise实例对象建好后能够调用then办法,而且是能够链式调用then办法,阐明then是一个实例办法。也就是说调用了then办法之后返回的也是一个promise实例对象

三、分步实现

明确了咱们须要做什么,那接下来二话不说开撸。然而咱们不可能一口气吃成一个瘦子,所以咱们须要依照下面写到的步骤一步步实现。

第一步:实现根本的框架

//先定义三个状态const PENDING = 'pending';const FULLFILLED = 'fullfilled';const REJECTED = 'rejected';class MyPromise {    constructor(fn) {          this.status = PENDING;      //初状态为pending        this.value = null;          //初始化value        this.reason = null;         //初始化reason              //定义resolve办法        this.resolve = (value) => {            if(this.status === PENDING) {                this.status = FULLFILLED;                this.value = value;            }        }        //定义rejecte办法        this.reject = (reason) => {            if(this.status === PENDING) {                this.status = REJECTED;                this.reason = reason;            }        }        //将resolve和reject作为参数调用传进来的参数,加上try...catch,一旦产生谬误就调用reject        try {            fn(this.resolve, this.reject);        } catch(error) {            this.reject(error);        }        this.then = this.then.bind(this);    }    then(onFullfilled, onRejected) {        //fullfilled状态则会调用onFullfilled函数        if(this.status === FULLFILLED) {            onFullfilled(this.value);        }        //rejected状态则会调用onRejected函数        if(this.status === REJECTED) {            onRejected(this.reason);        }    }}

这样,咱们根本的框架就曾经成形了,然而,then函数那里很显著是有缺点的,试想,如果传递给then函数的参数onFullfilled和onRejected如果不是一个函数呢,那这样必定会报错的,所以咱们then函数外面增加这样的一个判断:如果传递进来的参数不是一个函数,则间接返回value和reason的error。

then(onFullfilled, onRejected) {    //如果onFulfilled不是函数,给一个默认函数,返回value    let realOnFullfilled = onFullfilled;    if(typeof realOnFullfilled !== 'function') {      realOnFullfilled = (value) => value;    }    //如果onRejected不是函数,给一个默认函数,返回reason的error    let realOnRejected = onRejected;    if(typeof realOnRejected !== 'function') {      realOnRejected = (reason) => {        throw reason;      }    }    //fullfilled状态则会调用onFullfilled函数    if(this.status === FULLFILLED) {      onFullfilled(this.value);    }    //rejected状态则会调用onRejected函数    if(this.status === REJECTED) {      onRejected(this.reason);    }}

第二步:明确then办法是什么时候执行

如果只是像下面这样写的话,如果我执行上面的代码:

new Promise(fn).then(onFulfilled, onRejected);

这时候,不论fn的异步操作有没有完结,then办法都会马上执行,这显然是不合理的。咱们须要的是当fn外面的异步操作胜利或失败(即是status从pending变为fullfilled或者rejected)之后才会去执行then办法。如果要实现这个先后顺序该怎么办呢?

很简略,只有咱们限度onFulfilled和onRejected执行的机会就能够了,那该怎么限度呢?

很显著,咱们是须要等到status从pending变为fullfilled或者rejected才会去执行onFulfilled或onRejected。那如果fn的异步操作还没有实现(即是status还未从pending变为fullfilled或者rejected)怎么办?

很简略,那就把onFulfilled和onRejected先用一个数组暂存起来呗等到fn的异步操作实现即是status从pending变为fullfilled或者rejected),标记就是fn中的resolve或reject函数执行,再去执行onFulfilled或者onRejected就行了。

好了,剖析这么多,干吧!

这个是then办法外面补充的:

then(onFullfilled, onRejected) {  //省略其余的代码  //如果还是PENDING状态,将回调保留下来  if(this.status === PENDING) {    this.onFullfilledCallbacks.push(realOnFullfilled);    this.onRejectedCallbacks.push(realOnRejected);  }}

这个是在resolve办法和reject办法外面补充的:

//定义resolve办法this.resolve = (value) => {  if(this.status === PENDING) {    this.status = FULLFILLED;    this.value = value;    //fn状态由pending变成fullfilled,执行then外面的onFullfilled回调函数    this.onFullfilledCallbacks.forEach(callback => {      callback(this.value);    })  }}//定义rejecte办法this.reject = (reason) => {  if(this.status === PENDING) {    this.status = REJECTED;    this.reason = reason;    //fn状态由pending变成rejected,执行then外面的onRejected回调函数    this.onRejectedCallbacks.forEach(callback => {      callback(this.reason);    })  }}

好了,咱们曾经实现下面的构想了,当初能够来测试一波:

因为应用到了request模块,天然是要装上的:

npm install --save-dev request

因为在这里我应用了ES6,然而node还没有齐全反对ES6,所以这里配置了一下package.json:

{  "name": "test",  "version": "",  "type": "module",            //要将type设置为module,node就能够反对ES6而不须要装babel  "devDependencies": {    "babel-register": "^6.26.0",    "promises-aplus-tests": "^2.1.2",    "request": "^2.88.2"  },  "scripts": {    "test": "promises-aplus-tests MyPromise"  },  "dependencies": {    "koa": "^2.13.0"        //这个能够疏忽掉  }}

最初,在终端运行:

node MyPromiseTest.js

MyPromiseTest.js

import MyPromise from './MyPromise.js';import request from 'request';import babel from 'babel-register';let promise1 = new MyPromise((resolve) => {    request('https://www.baidu.com', (error, response) => {        if(!error && response.statusCode === 200) {            resolve('request1 success');        }    });});promise1.then((value) => console.log(value));let promise2 = new MyPromise((resolve, reject) => {    request('https://www.baidu.com', (error, response) => {        if(!error && response.statusCode === 200) {            reject('request2 failed');        }    });});promise2.then((value) => {    console.log(value);}, (reason) => {    console.log(reason);});

测试后果:

第一第二步测试通过,no problem!

第三步:实现then办法的返回值,使then办法可能链式调用

到这里为止,其实咱们曾经能够实现异步调用了,只是then的返回值还没实现,还不能实现链式调用。依据标准then的返回值必须是一个promise。依据要求的不同状况,抉择分步解决。

1.如果 onFulfilled 或者 onRejected 抛出一个异样 e ,则 promise2 必须拒绝执行,并返回拒因 e。

解决办法:每次执行onFulfilled 或者 onRejected新建一个promise,应用try...catch语句快捕捉到执行执行onFulfilled 或者 onRejected过程中抛出的谬误,并且应用reject办法解决它。

//如果还是PENDING状态,也不能间接保留回调办法了,须要包一层来捕捉谬误if(this.status === PENDING) {  // this.onFullfilledCallbacks.push(realOnFullfilled);  // this.onRejectedCallbacks.push(realOnRejected);  let promise2 = new MyPromise((resolve, reject) => {    this.onFullfilledCallbacks.push(() => {      try {        realOnFullfilled(this.value);      } catch(error) {        reject(error);      }    });    this.onRejectedCallbacks.push(() => {      try {        realOnRejected(this.reason);      } catch(error) {        reject(error);      }    });  });  return promise2;}//fullfilled状态则会调用onFullfilled函数if(this.status === FULLFILLED) {  let promise2 = new MyPromise((resolve, reject) => {    //这里不能简略的调用realOnFullfilled函数了,要应用try...catch语句块抱起来,如果有错就reject    try {      realOnFullfilled(this.value);    } catch(error) {      reject(error);    }  });  return promise2;}//rejected状态则会调用onRejected函数if(this.status === REJECTED) {  let promise2 = new MyPromise((resolve, reject) => {    //这里不能简略的调用realOnRejected函数了,要应用try...catch语句块抱起来,如果有错就reject    try {      realOnRejected(this.reason);    } catch(error) {      reject(error);    }  });  return promise2;}

2.如果 onFulfilled 不是函数且 promise1 胜利执行, promise2 必须胜利执行并返回雷同的值

解决办法:判断状态为FULLFILLED的时候,增加一层判断,判断onFullfilled是不是函数。如果不是,间接resolve,如果是,先执行onFullfilled函数,再resolve。

//这只是一个例子,每个onFullfilled函数都要这样写//fullfilled状态则会调用onFullfilled函数if(this.status === FULLFILLED) {  let promise2 = new MyPromise((resolve, reject) => {    //这里不能简略的调用realOnFullfilled函数了,要应用try...catch语句块抱起来,如果有错就reject    try {      //加一层onFullfilled是不是函数的判断      if(typeof onFullfilled !== 'function') {        resolve(this.value);        //返回雷同的值      } else {        realOnFullfilled(this.value);        resolve(this.value);      }    } catch(error) {      reject(error);    }  });  return promise2;}

3.如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回雷同的拒因。

解决办法:判断状态为REJECTED的时候,增加一层判断,判断onRejected是不是函数。如果不是,间接reject,如果是,先执行onRejected函数,再resolve。

//这只是一个例子,每个onRejected函数都要这样写//rejected状态则会调用onRejected函数if(this.status === REJECTED) {  let promise2 = new MyPromise((resolve, reject) => {    //这里不能简略的调用realOnRejected函数了,要应用try...catch语句块抱起来,如果有错就reject    try {      //加一层onRejected是不是函数的判断      if(typeof onRejected !== 'function') {        reject(this.reason);      } else {        realOnRejected(this.reason);        resolve();      }    } catch(error) {      reject(error);    }  });  return promise2;}

4.如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行上面的 Promise 解决过程:[[Resolve]](promise2, x)

解决办法:因为多了这一条规定,所以咱们还须要对胜利调用onFullfilled函数或onRejected函数的返回值做判断,如果有返回值就要进行Promise解决过程。所以咱们须要封装一个办法来进行Promise解决过程,暂且将这个办法命名为resolvePromise吧。

//这只是一个例子,其余类似的中央也要这样写//fullfilled状态则会调用onFullfilled函数if(this.status === FULLFILLED) {  let promise2 = new MyPromise((resolve, reject) => {    //这里不能简略的调用realOnFullfilled函数了,要应用try...catch语句块抱起来,如果有错就reject    try {      //加一层onFullfilled是不是函数的判断      if(typeof onFullfilled !== 'function') {        resolve(this.value);        //返回雷同的值      } else {        let x = realOnFullfilled(this.value);           //x是执行OnFullfilled函数的返回值        resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程      }    } catch(error) {      reject(error);    }  });  return promise2;}

第四步:Promise解决过程

规定太多了,都放进代码外面了,这个很简略,就是依照规定实现就好了

function resolvePromise(promise, x, resolve, reject) {    //如果promise和x指向同一对象,以TypeError为拒因拒绝执行promise    if(promise === x) {        return reject(new TypeError('The promise and the return value are the same'));    }    if(x instanceof MyPromise) {        //如果x为promise,则使promise承受x的状态        //也就是继续执行x,如果执行的时候拿到了一个y,还要持续解析y        x.then((y) => {            resolvePromise(promise, y, resolve, reject);        }, reject);    } else if(typeof x === 'object' || typeof x === 'function') {        //如果x为对象或函数        if(x === null) {            return resolve(x);        }        try {            //把x.then赋给then            var then = x.then;        } catch(error) {            return reject(error);        }        //如果then是函数        if(typeof then === 'function') {            let called = false;         //called示意resolvePromise或者rejectPromise被调用            //将x作为函数的作用域this调用            //传递两个回调函数作为参数,第一个参数叫做resolvePromise,第二个参数叫做rejectPromise            try {                then.call(                    x,                    //如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)                    //resolvePromise                    (y) => {                        //如果resolvePromise和rejectPromise均被调用                        //或者同一参数被调用了屡次,则优先采纳首次调用并疏忽剩下的调用                        if(called) return;                        called = true;                        resolvePromise(promise, y, resolve, reject);                    },                    (r) => {                        if(called) return;                        called = true;                        reject(r);                    }                );            } catch(error) {                //如果调用then办法抛出了异样e,                //如果resolvePromise或者rejectPromise曾经被调用,则疏忽之                if(called) return;                reject(error);            }        } else {            //如果then不是函数,以x为参数执行promise            resolve(x);        }    } else {        //如果x不为对象或函数,以x为参数执行promise        resolve(x);    }}

第五步:onFulfilled 和 onRejected 的执行机会

在标准中还有一条:onFulfilled 和 onRejected 只有在执行环境堆栈仅蕴含平台代码时才可被调用。这一条的意思是实际中要确保 onFulfilled 和 onRejected 办法异步执行且应该在 then 办法被调用的那一轮事件循环之后的新执行栈中执行。所以在咱们执行onFulfilled 和 onRejected的时候都应该包到setTimeout外面去

增加了setTimeout的then办法:

then(onFullfilled, onRejected) {  //如果onFulfilled不是函数,给一个默认函数,返回value  let realOnFullfilled = onFullfilled;  if(typeof realOnFullfilled !== 'function') {    realOnFullfilled = (value) => value;  }  //如果onRejected不是函数,给一个默认函数,返回reason的error  let realOnRejected = onRejected;  if(typeof realOnRejected !== 'function') {    realOnRejected = (reason) => {      throw reason;    }  }  //如果还是PENDING状态,也不能间接保留回调办法了,须要包一层来捕捉谬误  if(this.status === PENDING) {    // this.onFullfilledCallbacks.push(realOnFullfilled);    // this.onRejectedCallbacks.push(realOnRejected);    let promise2 = new MyPromise((resolve, reject) => {      this.onFullfilledCallbacks.push(() => {        //这里加setTimeout        setTimeout(() => {          try {            if(typeof onFullfilled !== 'function') {              resolve(this.value);            } else {              let x = realOnFullfilled(this.value);              resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程            }          } catch(error) {            reject(error);          }        }, 0);      });      this.onRejectedCallbacks.push(() => {        //这里加setTimeout        setTimeout(() => {          try {            if(typeof onRejected !== 'function') {              reject(this.reason);            } else {              let x = realOnRejected(this.reason);              resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程            }          } catch(error) {            reject(error);          }        }, 0);      });    });    return promise2;  }  //fullfilled状态则会调用onFullfilled函数  if(this.status === FULLFILLED) {    let promise2 = new MyPromise((resolve, reject) => {      //这里要增加setTimeout,保障回调在下一轮事件循环开始的时候执行      setTimeout(() => {        //这里不能简略的调用realOnFullfilled函数了,要应用try...catch语句块抱起来,如果有错就reject        try {          //加一层onFullfilled是不是函数的判断          if(typeof onFullfilled !== 'function') {            resolve(this.value);        //返回雷同的值          } else {            let x = realOnFullfilled(this.value);           //x是执行OnFullfilled函数的返回值            resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程          }        } catch(error) {          reject(error);        }      }, 0);    });    return promise2;  }  //rejected状态则会调用onRejected函数  if(this.status === REJECTED) {    let promise2 = new MyPromise((resolve, reject) => {      //这里要增加setTimeout,保障回调在下一轮事件循环开始的时候执行      setTimeout(() => {        //这里不能简略的调用realOnRejected函数了,要应用try...catch语句块抱起来,如果有错就reject        try {          //加一层onRejected是不是函数的判断          if(typeof onRejected !== 'function') {            reject(this.reason);          } else {            let x = realOnRejected(this.reason);           //x是执行OnRejected函数的返回值            resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程          }        } catch(error) {          reject(error);        }      }, 0);    });    return promise2;  }}

第六步:实现一个静态方法deferred

官网对这个办法的定义如下:

  • deferred: 返回一个蕴含{ promise, resolve, reject }的对象
  • promise 是一个处于pending状态的promise
  • resolve(value) 用value解决下面那个promise
  • reject(reason) 用reason回绝下面那个promise

代码如下:

MyPromise.deferred = () => {    let result = {};    result.promise = new MyPromise((resolve, reject) => {        result.resolve = resolve;        result.reject = reject;    });    return result;}

四、测试

1.先下载测试的工具:

npm install --save-dev promises-aplus-tests

2.重新配置package.json:

{  "name": "test",  "version": "",  //"type": "module",            //将这个删掉,因为测试应用的是CommonJS语法  "devDependencies": {    "babel-register": "^6.26.0",    "promises-aplus-tests": "^2.1.2",    "request": "^2.88.2"  },  "scripts": {    "test": "promises-aplus-tests MyPromise"  },  "dependencies": {    "koa": "^2.13.0"        //这个能够疏忽掉  }}

3.在MyPromise.js的文末将MyPromise抛出:

//省略下面的代码module.exports = MyPromise;

4.运行npm脚本:

npm run test

5.测试后果:

872用例全通过!!!

五、实现Promise其余的办法

1.Promise.resolve()

将现有对象转为Promise对象,如果 Promise.resolve 办法的参数,不是具备 then 办法的对象(又称 thenable 对象),则返回一个新的 Promise 对象,且它的状态为fulfilled。

实现代码如下:

resolve(parameter) {  if(parameter instanceof MyPromise) {    //如果是传入的参数是Promise对象,则返回对象自身    return parameter;  }  //如果传入的参数不是一个thenable对象,则返回一个新的promise对象,且状态变为fullfilled  return new MyPromise((resolve) => {    resolve(parameter);  })}

2.Promise.reject()

Promise.reject(reason)办法也会返回一个新的 Promise 实例,该实例的状态为rejected

实现代码如下:

reject(reason) {  //返回一个新的promise实例,该实例的状态为rejected  return new MyPromise((resolve, reject) => {    reject(reason);  })}

3.Promise.all()

该办法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

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

Promise.all()办法承受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用Promise.resolve办法,将参数转为 Promise 实例,再进一步解决。当p1, p2, p3全副resolve,大的promise才resolve,有任何一个reject,大的promise都reject

实现代码如下:

all(promiseList) {  return new MyPromise((resolve, reject) => {    var count = 0;      //用来统计promiseList中promise实例resolve的个数    var length = promiseList.length;    var result = [];    //用来寄存promiseList中resolved的promise的返回值    if(length === 0) {      //如果传递进来的是空数组,间接resolve      resolve(result);    }    promiseList.forEach((promise, index) => {      //将数组中的每一个元素都应用Promise.resolve()办法变为promise对象      MyPromise.resolve(promise).then((value) => {        count++;        result[index] = value;        if(count === length) {          //只有当所有的promise实例都resolve,新对象的状态才会变成resolved,          //并且将每个promise胜利后的返回值组成数组传递给新的promise对象的回调函数          return resolve(result);        }      }, (reason) => {        //只有有一个promise实例被reject,返回的promise对象的状态就变为rejected,        //且第一个被reject的实例对象的返回值会被传递给新的promise对象的回调函数        return reject(reason);      });    });  });}

4.Promise.race()

该办法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

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

该办法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。下面代码中,只有p1、p2、p3之中有一个实例率先扭转状态,p的状态就跟着扭转那个率先扭转的 Promise 实例的返回值,就传递给p的回调函数

实现代码:

race(promiseList) {  return new MyPromise((resolve, reject) => {    let length = promiseList.length;    if(length === 0){      return resolve();    }    promiseList.forEach((promise) => {      MyPromise.resolve(promise).then((value) => {        //只有有一个promise被resolve,返回的新的promise实例就被resolve        return resolve(value);      }, (reason) => {        //只有有一个promise被reject,返回的新的promise实例就被reject        return reject(reason);      });    });  });}

5.Promise.prototype.catch()

Promise.prototype.catch办法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定产生谬误时的回调函数。

实现代码如下:

catch(onRejected) {  this.then(null, onRejected);}

6.Promise.prototype.finally()

finally办法用于指定不论 Promise 对象最初状态如何,都会执行的操作。该办法是 ES2018 引入规范的。

实现代码如下:

finally(callback) {  //无论如何都会执行callback,并且返回原来的值  return this.then(    value => MyPromise.resolve(callback()).then(() => value),    reason => MyPromise.resolve(callback()).then(() => {throw reason})  )}

7.Promise.allSettled

该办法承受一组 Promise 实例作为参数,包装成一个新的 Promise 实例只有等到所有这些参数实例都返回后果,不论是fulfilled还是rejected,包装实例才会完结。该办法由 ES2020 引入。该办法返回的新的 Promise 实例,一旦完结,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,Promise 的监听函数接管到的参数是一个数组,每个成员对应一个传入Promise.allSettled()的 Promise 实例的执行后果

实现代码:

allSettled(promiseList) {  //只有当所有的promise实例都返回后果(不论是resolve还是reject)才会完结  //只会被resolve,不会被reject  return new MyPromise((resolve, reject) => {    var length = promiseList.length;    var result = [];    var count = 0;    if(length === 0) {      return resolve(result);    }    promiseList.forEach((promise, index) => {      MyPromise.resolve(promise).then((value) => {        count++;        result[index] = {          status: 'fullfilled',          value: value        };        if(count === length) {          return resolve(result);        }      }, (reason) => {        count++;        result[index] = {          status: 'rejected',          reason: reason        };        if(count === length) {          return resolve(result);        }      });    });  });}

8.Promise.any()

用法:

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

Promise.any()办法承受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

实现代码:

any(promiseList) {  //只有所有的promise都被reject时,新的promise对象才会被reject  //一旦由promise被resolve时,新的promise对象就被resolve  return new MyPromise((resolve, reject) => {    var length = promiseList.length;    var count = 0;    var result = [];    if(length === 0) {      return resolve(result);    }    promiseList.forEach((promise, index) => {      MyPromise.resolve(promise).then((value) => {        return resolve(value);      }, (reason) => {        count++;        result[index] = reason;        if(count === length) {          return reject(result);        }      });    });  });}

六、总结

写这篇货色写了一整天了,也终于实现了一个比拟残缺的Promise,写完之后发现自己对Promise的理解变得更加透测,感觉很多货色还是须要看懂源码并且本人应用原生js实现一遍能力真正透测的了解它。因为源码过长,所以我将它推上了GitHub,附上源码的地址:https://github.com/lugezuishuai/Promise-realization