一步一步实现符合A+规范的promise

实现构造函数

const promise = new Promsie((resolve, reject) => {
    // ...
})
复制代码

promise的状态

promise有三种状态,pending、fulfilled、rejected。而且状态一旦由初始状态(pending)变为其余状态(fulfilled、rejected)便不能再次更改npm

promise的执行器

promise的执行器executor是构造函数的function类型的参数,executor接收两个参数resolve, reject设计模式

resolve

resolve是promise内部实现的函数,接受一个参数,promise会将该参数存储起来,将该参数做为参数传递给then方法成功回调函数的参数,将promise的状态由pending改变为fulFilled;promise

rejected

reject一样是promise内部实现的函数也接受一个参数,promise会将该参数存储起来,将该参数做为参数传递给then方法失败回调函数的参数,将promise的状态由pending改变为rejected。框架

实现constructor的代码异步

class Promise {
    constructor(executor) {
        this.state = 'pending'; // 初始化为pending状态
        this.value = undefined; // 存储resolve接受的参数
        this.reason = undefined; // 存储reject接受的参数
        const resolve = val => { // resovle的内部实现
            if (this.state === 'pending') { // 只有处于pending状态才能更改状态,防止二次更改状态
                this.value = val; 
                this.state = 'fulfilled'; //更改状态
            }
        }
        const reject = err => { // reject的内部实现
            if (this.state === 'pending') { // 只有处于pending状态才能更改状态,防止二次更改状态
                this.reason = err;
                this.state = 'rejected'; // 更改状态
            }
        }
        executor(resolve, reject); // 执行执行器
    }
}
复制代码

执行器出错

在开发中,若是executor中出现了错误,promise的处理:调用reject,并将错误做为参数传递给reject函数

修改代码测试

class Promise {
    constructor(executor) {
        ...
        try {
            executor(resolve, reject); // 执行执行器
        } catch(error) {
            reject(error);
        }
    }
}
复制代码

实现then方法

then方法接受两个函数参数,分别是成功状态的回调函数和失败状态的回调函数。ui

class Promise {
    constructor(executor) {
        ...
    }
    then(onFulfilled, onRejected) {
        if (this.state === 'fulfilled') { // 成功时调用成功状态的回调
            onFulfilled(this.value);
        }
        if (this.state === 'rejected') { // 失败时调用失败状态的回调
            onRejected(this.reason);
        }
    }
}
复制代码

如今能够看到,promise的三种状态是会频繁用到的,若是每次判断状态都用手写的字符串可能会有将状态字符创写错的状况。为了不这种错误,咱们用三个常量表示这三种状态。this

重构后的代码以下spa

/* 增长的代码 */
const PENDING = 'PENDING'; // 初始状态
const FULFILLED = 'FULFILLED'; // 成功状态
const REJECTED = 'REJECTED'; // 失败状态
/* 增长的代码 */
class Promise {
    constructor(executor) {
        this.state = PENDING; // 初始化为pending状态
        this.value = undefined; // 存储resolve接受的参数
        this.reason = undefined; // 存储reject接受的参数
        const resolve = val => { // resovle的内部实现
            if (this.state === PENDING) { // 只有处于pending状态才能更改状态,防止二次更改状态
                this.value = val; 
                this.state = FULFILLED; //更改状态
            }
        }
        const reject = err => { // reject的内部实现
            if (this.state === PENDING) { // 只有处于pending状态才能更改状态,防止二次更改状态
                this.reason = err;
                this.state = REJECTED; // 更改状态
            }
        }
        try {
            executor(resolve, reject); // 执行执行器
        } catch(error) {
            reject(error);
        }
    }
    then(onFulfilled, onRejected) {
        if (this.state === FULFILLED) { // 成功时调用成功状态的回调
            onFulfilled(this.value);
        }
        if (this.state === REJECTED) { // 失败时调用失败状态的回调
            onRejected(this.reason);
        }
    }
}
复制代码

同一个promise的then方法能够调用屡次

promise A+规范:同一个promise的then方法能够调用(非链式调用)屡次且每次调用的结果相同

let p = new Promise(resolve => {
    resolve(1);
})
p.then(data => {
    console.log(data) // 输出1
})
p.then(data => {
    console.log(data) // 输出1
})
复制代码

因为咱们在调用resolve或者reject时已经将参数存储在promise内部,因此咱们的代码符合这条规则

resolve或reject的调用处于异步代码块中

如今有一个问题,用过promise的小伙伴都知道,在new一个Promise的时候,若是resolve或者reject的调用处于异步代码块中,then方法也是能够在resolve以后正常执行的。而如今咱们的代码明显是没法处理resolve或者reject的调用处于异步代码块中的状况。由于若是resolve或者reject的调用处于异步代码块中,当调用then方法是promise还处于pending状态。

那如何解决这个问题呢?咱们能够想到:若是promise处于pending状态时,能够先将then方法接收的回调函数先存储起来,等promise的状态由pending变为fulfilled或者rejected时再调用相应地回调函数。看到这里有没有想起一种很经常使用的设计模式?这种模式的描述:它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,全部依赖于它的对象都将获得通知。没错,就是观察者模式或者说是发布订阅模式。

再来修改咱们的代码

首先是修改构造函数

constructor(executor) {
    this.state = PENDING; 
    this.value = undefined;
    this.reason = undefined;
    /* 增长的代码 */
    this.onFulfilledCallbacks = []; // 存储成功状态的回调函数
    this.onRejectedCallbacks = []; // 存储失败状态的回调函数
    /* 增长的代码 */
    const resolve = val => { 
        if (this.state === PENDING) { 
            this.value = val; 
            this.state = FULFILLED;
            /* 增长的代码 */
            this.onFulfilledCallbacks.forEach(fn => fn(this.value)) // 状态改变,通知回调函数执行
            /* 增长的代码 */
        }
    }
    const reject = err => { 
        if (this.state === PENDING) { 
            this.reason = err;
            this.state = REJECTED;
            /* 增长的代码 */
            this.onRejectedCallbacks.forEach(fn => fn(this.reason)) // 状态改变,通知回调函数执行
            /* 增长的代码 */
        }
    }
    executor(resolve, reject); // 执行执行器
}
复制代码

修改then方法

then(onFulfilled, onRejected) {
    if (this.state === FULFILLED) { 
        onFulfilled(this.value);
    }
    if (this.state === REJECTED) { 
        onRejected(this.reason);
    }
    /* 增长的代码 */
    if (this.state === PENDING) {
        this.onFulfilledCallbacks.push(onFulfilled); // 存储回调函数
        this.onRejectedCallbacks.push(onRejected); // 存储回调函数
    }
    /* 增长的代码 */
}
复制代码

如今咱们来测试一下

let promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve(1);
  }, 1000)
})
promise.then(data => {
  console.log(data) 
})
// 结果:打印1
复制代码

resolve的参数是一个promise实例

若是resolve的参数是一个promise实例,那么须要取得其最终(可能嵌套多层promise)的值

修改后的constructor

constructor(executor) {
    this.value = undefined;
    this.reason = undefined;
    this.status = PENDING;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    let resolve = (value) => { // 将promise的状态改成fulfilled状态
      if (value instanceof Promise) { // 递归解析resolve传入的值是promise的状况
        return value.then(resolve, reject);
      }
      if (this.status === PENDING) { // 保证状态一旦改变便不能再次改变
        this.value = value;
        this.status = FULFILLED;
        this.onFulfilledCallbacks.forEach(onFulfilled => {
          onFulfilled();
        })
      }
    }
    let reject = (reason) => { // 将promise的状态改成rejected状态
      if (this.status === PENDING) { // 保证状态一旦改变便不能再次改变
        this.reason = reason;
        this.status = REJECTED;
        this.onRejectedCallbacks.forEach(onRejected => {
          onRejected();
        })
      }
    }
    try { // 执行器函数有可能会有异常,若是出现异常,执行reject
      executor(resolve, reject)
    } catch (e) {
      reject(e);
    }
}
复制代码

到了这里小伙伴可能会想到,reject是否也要作一样的处理?并不须要。promiseA+规范规定reject直接将传入的失败的缘由传出便可。这也是很是符合逻辑的,由于一旦reject,就代表出现了错误,并不须要得到数据。而resolve的职责是将数据传出,因此才须要递归解析来得到其最终的值,而不是一个promise实例。

传入的onFulfilled或者onRejected不是函数

若是onFulfilled或者onRejected不是函数,那么咱们须要将其从新赋值

then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val; 
    onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err};
    if (this.state === FULFILLED) { 
        onFulfilled(this.value);
    }
    if (this.state === REJECTED) { 
        onRejected(this.reason);
    }
    /* 增长的代码 */
    if (this.state === PENDING) {
        this.onFulfilledCallbacks.push(onFulfilled); // 存储回调函数
        this.onRejectedCallbacks.push(onRejected); // 存储回调函数
    }
    /* 增长的代码 */
}
复制代码

实现then方法的链式调用

在实现链式调用以前,须要知道的then的详细用法。then的链式调用中

  1. 前一个then的回调函数的返回值会传递给下一个then的回调函数的参数;
  2. 若是前一个then方法的回调函数在执行的过程当中发生错误,将此错误做为参数传递给下一个then方法,下一个then方法执行失败的回调。
  3. 若是前一个then的返回值是一个promise,
    1. 若是返回的promise是fulfilled状态,则下一个then方法执行成功的回调,
    2. 若是返回的promise是rejected状态,则下一个then方法执行失败的回调,
    3. 若是返回的promise是pending状态,那么下面的then方法都不会执行;

咱们先不考虑then方法的回调函数的返回值是promise的状况,先实现1,2

既然可以链式调用,那么then方法的返回值一定是一个Promise实例。那么返回的promise实例是否是自身呢?答案显而易见:不是。若是一个promise的then方法的返回值是promise自身,在new一个Promise时,调用了resolve方法,由于promise的状态一旦更改便不能再次更改,那么下面的全部then便只能执行成功的回调,没法进行错误处理,这显然并不符合promise的规范和设计promise的初衷。

而实际的then方法的链式调用规则是这样的:若是promise的前一个then方法执行的是成功(或者失败)的回调,那么下一个then方法有可能执行成功的回调,也有可能执行失败的回调,显然then返回的是一个新的promise实例。

修改then方法

then(onFulfilled, onRejected) {
    const promise2 = new Promise((resolve, reject) => { // 待返回的新的promise实例
        if (this.state === FULFILLED) {
            try {
                let x = onFulfilled(this.value); 
                resolve(x); // 取得promise的then方法回调函数的返回值,将其存储到promise2中
            } catch(error) {
                reject(error) // 若是出错这次的then方法的回调函数出错,在将错误传递给promise2
            }
        }
        if (this.state === REJECTED) {
            try {
                let x = onRejected(this.reason);
                resolve(x);
            } catch (error) {
                reject(error);
            }
        }
        if (this.state === PENDING) {
            this.onFulfilledCallbacks.push(() => {
                try {
                    let x = onFulfilled(this.value);
                    resolve(x);
                }
            }); 
            this.onRejectedCallbacks.push(() => {
                try {
                    let x = onRejected(this.reason);
                    resolve(x);
                } catch (error) {
                    reject(error);
                }
            }); 
        }
    })
    return promise2;
}
复制代码

处理then方法的回调函数的返回值是promise实例的状况

因为有多处须要处理回调函数的返回值,因此须要将处理的过程封装成一个方法。咱们把这个方法命名为resolvePromise。接下来咱们思考resolvePromise这个方法须要哪些参数呢?x(回调函数的返回值)确定是必须的,在方法体内还须要更改promise2的状态,因此须要将resovle和reject也传递进去。还有一个必须的参数,那就是promise2自己。

为何须要将promise2也传递进去?

由于要防止一种逻辑上的错误:就是x与promise2引用的是同一个对象。

let p = new Promise(resolve => {
    resolve(1);
});
let p1 = p.then(data => {
    return p1;
})
复制代码

若是你测试上述代码,则会看到以下报错信息

UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise> 复制代码

这在逻辑上上行不通的,若是promise2 === x,那么就至关于你本身在等着你本身干完某件事情,而你不作任何事情仅仅是在等着,这是一个悖论,你要完成的事情永远也没法完成。在代码实现上就是判断promise2和x是否引用了同一个对象,若是相同则抛出一个TypeError(这是promiseA+规范规定的)

接下来就是处理x是promise仍是普通值。

那么如何断定某个变量是一个promise实例呢?分为3步:

  1. 判断该变量是不是对象或者是函数类型
  2. 若是知足1,判断该变量是否有then属性
  3. 若是知足2,判断then是不是一个函数

若是三条都知足,则认为这是一个promise的实例,不然就是一个普通值

一句话总结如何判断某个变量是不是promise的实例:该变量是不是一个拥有then方法的对象或者函数。虽然这样判断并不必定准确(甚至对某些js库产生了影响),可是promiseA+规范是如此规定的。

接下的须要作的事情就很清晰了。x若是不是promise的实例,直接调用传递进来的resolve将x传出;若是x是promise的实例,那么就调用x的then方法,将x(注意此时的x是一个promise实例)存储的成功的值或者失败的缘由取出,再对取出的值进行一次解析,直到取出的值为普通值。

在处理x是promise的状况中,有几点须要注意的地方:

  1. 处理过程当中可能会出错,若是出错就调用reject将错误传出
  2. 有可能在应用中你采用的promise和第三方框架中的promise不一样,第三方框架中的promsie不必定彻底知足promiseA+规范,若是不作处理,有可能出现状态二次改变的状况。因此要防止promise状态的二次改变,咱们须要一个标志,表示x的状态是否已经改变,若已经改变则不往下进行,直接返回。
  3. resolvePromise中须要传入promise2,而resolvePromise是在promise2生成的过程当中调用的,也就是说promise2尚未生成,就要对其进行访问,这确定是行不通的。因此咱们能够将resolvePromise放到异步代码块中来解决这个问题。

修改后的then方法

then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val; 
    onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err};
    let promise2 = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err)
          }
        })
      }
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        })
      }
      // 当promise的执行器中有异步代码的时候,而且触发状态改变的resolve或者reject
      // 在异步代码块中。这种状况下,因为先执行了then,而promise的状态仍是PENDING状态
      // 因此then方法内的函数便没法执行。
      // 此时可先将then方法内的函数参数存储起来,利用订阅-发布模式,当执行器中等的异步代码
      // 执行后,触发存储起来的函数的执行。
      if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err)
            }
          })
        })
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          })
        })
      }
    })
    return promise2;
}
复制代码

resolvePromise的实现代码

const resolvePromise = function (promise2, x, resolve, reject) {
  if (x === promise2) { // x和promise引用同一个对象的状况
    throw new TypeError('Chaining cycle detected for promise #<Promise>');
  }
  let isCalled = false; // 判断是否已经resolve或者reject,防止状态二次改变
  // 判断x是不是一个promise:
  // 1.判断是不是对象或者function
  // 2.判断是否有then,而且then是一个方法
  if ((x !== null && typeof x === 'object') || typeof x === 'function') {
    try { // 考虑一种很极端的状况,即then属性的getter方法内抛出异常,就是一取x.then就报错
      let then = x.then;
      if (typeof then === 'function') {
        then.call(x, (y) => { // 之因此不直接再次x.then,也是考虑到一个极端的状况,就是传入的x的then方法第一次访问不报错,当第二次访问的时候就报错(then是一种高阶函数)
          if (isCalled) return;
          isCalled = true;
          resolvePromise(promise2, y, resolve, reject); // 递归解析
        }, (r) => {
          if (isCalled) return; // 若是已经调用过了resolve
          isCalled = true;
          reject(r);
        }) // 考虑到高阶函数当执行第二次后可能有不一样的行为
      } else { // then属性不是函数
        resolve(x); // 将值传出
      }
    } catch (err) {
      if (isCalled) return; // 若是已经调用过了resolve
      isCalled = true;
      reject(err); // 将错误传出
    }
  } else {
    resolve(x); // 将值传出
  }
}
复制代码

promise完整实现

测试promise是否符合规范:

第一步,经过yarn或者npm全局安装promises-aplus-tests包

第二步,在实现promise的文件中加入以下代码

Promise.defer = Promise.deferred = function(){
  let dfd = {};
  dfd.promise = new Promise((resolve,reject)=>{
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
}
复制代码

第三步,运行命令promises-aplus-tests 实现promise的js文件路径

完整实现的代码

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
  constructor(executor) {
    this.state = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.fulfilledCallbacks = []; // 存储
    this.rejectedCallbacks = [];
    const resolve = (value) => {
      if (value instanceof Promise) {
        return value.then(resolve, reject);
      }
      if (this.state === PENDING) {
        this.value = value;
        this.state = FULFILLED;
        this.fulfilledCallbacks.forEach(fn => fn());
      }
    }
    const reject = (reason) => {
      if (this.state === PENDING) {
        this.reason = reason;
        this.state = REJECTED;
        this.rejectedCallbacks.forEach(fn => fn())
      }
    }
    try {
      executor(resolve, reject);
    } catch(error) {
      reject(error);
    };
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err};
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === FULFILLED) {
        setTimeout(() => {
          try {
            // 因为onFulfilled 或 onRejected的返回值多是promise
            // 故须要一个特殊的方法来处理各类状况
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch(error) {
            reject(error);
          }
        })
      }
      if (this.state === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        })
      }
      if (this.state === PENDING) {
        this.fulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch(error) {
              reject(error)
            }
          })
        });
        this.rejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch(error) {
              reject(error);
            }
          })
        });
      }
    })
    return promise2;
  }
}
// 因为onFulfilled 或 onRejected的返回值多是promise
// 故须要一个特殊的方法来处理各类状况
function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
  }
  let isCalled = false;
  if (typeof x === 'object' && x !== null || typeof x === 'function') {
    try {
      const then = x.then;
      if (typeof then === 'function') {
        then.call(x, y => {
          if (isCalled) {
            return;
          }
          isCalled = true;
          resolvePromise(promise2, y, resolve, reject);
        }, r => {
          if (isCalled) {
            return;
          }
          isCalled = true;
          reject(r);
        })
      } else {
        resolve(x);
      }
    } catch (error) {
      if (isCalled) {
        return;
      }
      isCalled = true;
      reject(error);
    }
  } else {
    resolve(x);
  }
}
Promise.defer = Promise.deferred = function(){
  let dfd = {};
  dfd.promise = new Promise((resolve,reject)=>{
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
}
复制代码
相关文章
相关标签/搜索