Skip to content

Promise A+ 手写一个 Promise

Promise 规范文档 https://promisesaplus.com/

Promise 术语

  • promise is an object or function with a then method.
  • thenable is an object or function that defines a then method.
  • value is any legal JavaScript value (including undefined, a thenable, or a promise).
  • exception is a value that is thrown using the throw statement.
  • reason is a value that indicates why a promise was rejected.

需求

Promise States

一个 promise 有三中状态:pending, fulfilled, 或 rejected。

  • 当 pending,可转化为 fulfilled 或 rejected。
  • 当 fulfilled,状态不会再改变,记录一个 value 值。
  • 当 rejected,状态不会再改变,记录一个 reason 值。

new Promise

  • new Promise 时,需要传递一个 executor 执行器,执行器立刻执行
  • executor 接受两个参数,分别是 resolve 和 reject

then Method

then 方法有两个参数,分别访问自己当前或最终的 value or reason。

promise.then(onFulfilled, onRejected)
  • 如果调用 then 时,promise 已经成功,则执行 onFulfilled,并将 promise 的 value 作为参数传递进去。
  • 如果promise已经失败,那么执行 onRejected, 并将 promise 失败的 reason 作为参数传递进去。
  • 如果 promise 的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行(发布-订阅)。
  • promise 可以 then 多次,promise 的 then 方法返回一个 promise。
  • 如果 then 返回的是一个结果,那么就会把这个结果作为参数,传递给下一个 then 的成功的回调(onFulfilled)。
  • 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调(onRejected)。
  • 如果 then 返回的是一个 promise,那么需要等这个 promise 执行完(递归);如果成功,就走下一个then的成功,如果失败,就走下一个then的失败。

Promise Resolution Procedure

function resolvePromise(promise2, x, resolve, reject)
  • 若 promise2 等于 x, reject 一个 TypeError。(死循环)
  • 若 x 是一个 promise, 观察其状态:(递归)
    • 如果 x 是 pending,等到 x fulfilled or rejected;
    • 如果 x 是 fulfilled,resolve x 的 value;
    • 如果 x 是 rejected,rejected x 的 reason。
  • 如果 x 是 object or function,
    • 让 then = x.then
    • 如果 then 是 function,调用 then,调用者为 x,第一个参数 resolvePromise, 第二个参数 rejectPromise:
      • resolvePromise 的参数是 y, 递归([[Resolve]])(promise2, y);
      • rejectPromise 的参数是 r, 直接 reject(r );
    • 如果 then 不是 function,resolve(x);
  • 如果 x 不是 object or function,resolve(x);

An Example

var p = new Promise((resolve, reject) => {
  // resolve(100);
  setTimeout(() => {
    resolve(110);
  }, 1000);
  // reject(200);
  // throw new Error(300);
});

p.then(
  val => {
    console.log('in then1:', val);
    return new Promise((resolve, reject) => {
      // resolve('then1的结果');
      // reject('then1的原因');
      resolve(new Promise(resolve => resolve('then1的嵌套结果')));
    });
  },
  reason => {
    console.log('in err1:', reason)
    return 'err1 的结果';
  }
).then(
  val => {
    console.log('in then1.1:', val)
    return 'then1.1的结果';
  },
  reason => {
    console.log('in err 1.1:', reason);
  }
).then(
  val => console.log('in then1.1.1:', val),
  reason => console.log('in err1.1.1:', reason)
);

p.then(
  val => console.log('in then2:', val),
  reason => console.log('in err2:', reason)
);

核心代码

// src/promise.js
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

function Promise(executor) {
  var self = this;
  self.status = PENDING;
  self.onFulfilledList = [];
  self.onRejectedList = [];
  self.value = undefined;
  self.reason = undefined; 

  function resolve(value) {
    if (self.status === PENDING) {
      self.status = FULFILLED;
      self.value = value;
      self.onFulfilledList.forEach(fn => fn());
    }
  }

  function reject(reason) {
    if (self.status === PENDING) {
      self.status = REJECTED;
      self.reason = reason;
      self.onRejectedList.forEach(fn => fn());
    }
  }

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

// promise a+ 2.2
Promise.prototype.then = function (onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
  const self = this;

  let promise2 = new Promise((resolve, reject) => {
    if (self.status === FULFILLED) {
      /* promise a+ 2.2.4 --> setTimeout
       * onFulfilled or onRejected must not be called
       * until the execution context stack contains only platform code.
       */
      setTimeout(() => { // 宏任务
        try {
          let x = onFulfilled(self.value);
          resolvePromise(promise2, x, resolve, reject);
        } catch(e) {
          reject(e);
        }
      });   
    }
    if (self.status === REJECTED) {
      setTimeout(() => { // 宏任务
        try {
          let x = onRejected(self.reason);
          resolvePromise(promise2, x, resolve, reject);
        } catch(e) {
          reject(e);
        }
      });
    }
    if (self.status === PENDING) {
      self.onFulfilledList.push(() => {
        setTimeout(() => { // 宏任务
          try {
            let x = onFulfilled(self.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch(e) {
            reject(e);
          }
        }); 
      });
      self.onRejectedList.push(() => {
        setTimeout(() => { // 宏任务
          try {
            let x = onRejected(self.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch(e) {
            reject(e);
          }
        })
      });
    }
  });

  return promise2;
};

// promise a+ 2.3
function resolvePromise(promise2, x, resolve, reject) {
  // promise a+ 2.3.1
  if (promise2 === x) {
    reject(new TypeError('Chaining cycle detected for promise'));
    return;
  }
  // promise a+ 2.3.3
  if (typeof x === 'object' && x !== null || typeof x === 'function') {
    let called; // promise a+ 2.3.3.3.3 防止成功和失败重复调用
    try {
      let then = x.then;
      if (typeof then === 'function') {
        // promise a+ 2.3.3.3
        then.call(x, y => {
          // resolve(y); // 直接 fulfill: y 是普通值
          if (called) { return; }
          called = true;
          resolvePromise(promise2, y, resolve, reject); // 递归: y 是 promise
        }, r => {
          if (called) { return; }
          called = true;
          reject(r);
        });
      } else {
        if (called) { return; }
        called = true;
        resolve(x); // 递归退出条件
      }
    } catch(e) {
      if (called) { return; }
      called = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
}

可将宏任务 setTimeout 替换为微任务API。

谷歌浏览器的 queueMicrotask 类似 nodejs 的 process.nextTick

Promise Deferred

代码

// src/promise.js 添加入口 
Promise.defer = Promise.deferred = function () {
  let dfd = {};
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
};

用途与测试

  • 可以减少一层嵌套。
const fs = require('fs');

function readFile(path) {
  let dfd = Promise.defer();
  fs.readFile(path, 'utf-8', (err, data) => {
    if (err) { dfd.reject(err); }
    dfd.resolve(data); 
  });
  return dfd.promise;
}

readFile('./demo.txt').then(data => console.log(data));

Promise A+ 测试用例

  • 前提1 添加入口 module.exports = Promise;
  • 前提2 实现 Promise.deferred
$ promises-aplus-tests src/promise.js

Promise.all

  • 全部成功才算成功,否则一个失败就返回失败。

代码

function isPromise(val) {
  return (
    typeof val === 'function' ||
    typeof val === 'object' && val !== null
  ) &&  typeof val.then === 'function'
}

Promise.all = function(arr) {
  return new Promise((resolve, reject) => {
    let result = [];
    let index = 0;
    for(let i = 0; i < arr.length; i++) {
      if (isPromise(arr[i])) {
        arr[i].then(value => {
          result[i] = value;
          if(++index === arr.length) {
            resolve(result);
          }
        }, reject)
      } else {
        result[i] = arr[i];
        if(++index === arr.length) {
          resolve(result);
        }
      }
    }
  });
}

测试

Promise.all([1, 2, readFile('./demo.txt'), 4 ]).then(value => console.log(value));

Promise.resolve

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

Promise.reject

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