Promises/A+ 规范代码学习

代码实现

promise伪代码的实现,要点有三。

  1. 处理executor中异步调用resolvereject和多次调用then函数问题。
  2. p2定义的过程中根据语法逻辑,不能直接同步访问p2,通过setTimeout异步的方式传递p2 (p2then方法返回的新promise实例,用来实现promise的链式调用)。
  3. 考虑x为一个promise实例以及递归处理promise嵌套。

参考规范:Promises/A+

// index.js
const pending = 'pending';
const fulfilled = 'fulfilled';
const rejected = 'rejected';

const isFunction = (value) => typeof value === 'function';
class MyPromise {
  constructor(executor) {
    this.status = pending;
    this.value = undefined;
    this.reason = undefined;

    this.onFulFilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (value instanceof MyPromise) {
        value.then(resolve, reject);
        return;
      }
 
      if (this.status === pending) {
        this.status = fulfilled;
        this.value = value;
        this.onFulFilledCallbacks.forEach(fn => fn());
      }
    };

    const reject = (reason) => {
      if (this.status === pending) {
        this.status = rejected;
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch(e) {
      reject(e);
    }
  }

  then(onFulFilled, onRejected) {
    onFulFilled = isFunction(onFulFilled) ? onFulFilled : data => data;
    onRejected = isFunction(onRejected) ? onRejected : err => { throw err; };

    const p2 = new MyPromise((resolve, reject) => {
      let x;
      if (this.status === fulfilled) {
        setTimeout(() => {
          try { 
            x = onFulFilled(this.value);
            resolvePromise(p2, x, resolve, reject);
          } catch(err) {
            reject(err);
          }
        }, 0);
      }
  
      if (this.status === rejected) {
        setTimeout(() => {
          try {
            x = onRejected(this.reason);
            resolvePromise(p2, x, resolve, reject);
          } catch(err) {
            reject(err);
          }
        }, 0);
      }

      if (this.status === pending) {
        this.onFulFilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              x = onFulFilled(this.value);
              resolvePromise(p2, x, resolve, reject);
            } catch(err) {
              reject(err);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              x = onRejected(this.reason);
              resolvePromise(p2, x, resolve, reject);
            } catch(err) {
              reject(err);
            }
          }, 0);
        });
      }
    });

    return p2;
  }

  catch(errorCallback) {
    return this.then(null, errorCallback);
  }
  
  static resolve(value) {
    return new MyPromise((resolve, reject) => {
      resolve(value);
    });
  }

  static reject(error) {
    return new MyPromise((resolve, reject) => {
      reject(error);
    });
  }
}

function resolvePromise(p2, x, resolve, reject) {
  let called = false;
  if (p2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
  }

  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    try {
      let then = x.then;

      if (typeof then === 'function') {
        then.call(x, (y) => {
          if (called) return;
          called = true;
          resolvePromise(p2, y, resolve, reject);
        }, (r) => {
          if (called) return;
          called = true;
          reject(r);
        });
      } else {
        if (called) return;
        called = true;
        resolve(x);
      }
    } catch(err) {
      if (called) return;
      called = true;
      reject(err);
    } 
  } else {
    resolve(x);
  }
};

A+规范测试

工具安装

yarn add promises-aplus-tests -g

配置代码

// index.js
MyPromise.defer = MyPromise.deferred = function () {
  let dfd = {};
  dfd.promise = new MyPromise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
}

module.exports = MyPromise;

测试命令

promises-aplus-tests index

promises-aplus-tests

Promise周边

all

class MyPromise {
	// ...
  static all (promiseArr) {
    let resArr = [];
    let idx = 0;

    return new MyPromise((resolve, reject) => {
      promiseArr.map((promise, index) => {
        if (isPromise(promise)) {
          promise.then((res) => {
            formatResArr(res, index, resolve);
          }, reject);
        } else {
          formatResArr(promise, index, resolve);
        }
      });
    });

    function formatResArr (value, index, resolve) {
      resArr[index] = value;

      if (++ idx === promiseArr.length) {
        resolve(resArr);
      }
    }
  }
}

function isPromise(x) {
  if ((typeof x === 'object' && x !== null ) || typeof x === 'function') {
    let then = x.then;

    return typeof then === 'function';
  }

  return false;
}

allSettled

class MyPromise {
	// ...
  static allSettled(promiseArr) {
    let resArr = [];
    let idx = 0;

    if (!isIterable(promiseArr)) {
      throw new TypeError(promiseArr + ' is not iterable (cannot read property Symbol(Symbol.iterator))');
    }

    return new MyPromise((resolve, reject) => {
      if (promiseArr.length === 0) {
        resolve([]);
      }

      promiseArr.map((promise, index) => {
        if (isPromise(promise)) {
          promise.then((value) => {
            formatResArr('fulfilled', value, index, resolve);
          }, (reason) => {
            formatResArr('rejected', reason, index, resolve);
          });
        } else {
          formatResArr('fulfilled', promise, index, resolve);
        }
      });
    });

    function formatResArr (status, value, index, resolve) {
      switch (status) {
        case "fulfilled":
          resArr[index] = {
            status,
            value
          };
          break;
        case "rejected":
          resArr[index] = {
            status,
            reason: value
          };
          break;
        default:
          break;
      }

      if (++idx === promiseArr.length) {
        resolve(resArr);
      }
    }
  }
}

function isIterable (value) {
  return value !== null && value !== undefined && typeof value[Symbol.iterator] === 'function';
}

race

class MyPromise {
	// ...
  static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
      promiseArr.map((promise) => {
        if (isPromise(promise)) {
          promise.then(resolve, reject);
        } else {
          resolve(promise);
        }
      });
    });
  }
}

finally

  1. finally无论外面的promise成功还是失败,都要走。并且回调不带参数
  2. 正常走finally之后then或者catch
  3. 如果finally内部有promise,并且有延时处理,整个finally会等待
  4. 如果内外都是成功,取外面的结果
  5. 如果外面是成功,里面是失败,取里面的结果(失败)
  6. 如果外面是失败,里面是成功,取外面的结果(失败)
  7. 如果外面是失败,里面是失败,取里面的结果(失败)
  8. 如果外面是成功,里面是成功,取外面的结果(成功)
class MyPromise {
	// ...
  finally(finallyCallback) {
    return this.then((value) => {
      return MyPromise.resolve(finallyCallback()).then(() => value);
    }, (reason) => {
      return MyPromise.resolve(finallyCallback()).then(() => {
        throw reason;
      });
    });
  }
}

promisify

module.exports = {
  promisify (fn) {
    return function (...args) {
      return new MyPromise((resolve, reject) => {
        fn(...args, (error, data) => {
          if (error) {
            return reject(error);
          }
          resolve(data);
        });
      });
    }
  },
  promisifyAll (fns) {
    Object.keys(fns).forEach((fnName) => {
      if (typeof fns[fnName] === 'function') {
        fns[fnName + 'Async'] = this.promisify(fns[fnName]);
      }
    });
    return fns;
  }
}

> cd ..