1

Promise

目标

  • Promise A+规范
  • 手写Promise
  • 题目练习

Promise A+规范

术语

  1. promise: 一个拥有符合这个规范的行为的then方法的对象或函数。
  2. thenable: 定义了一个then方法的对象或函数。
  3. value: 任意合法的JavaScript值(包括undefined,thenable,promise)。
  4. exception: 使用throw语句抛出的一个值
  5. reason: 表示promise为什么被拒绝的一个值

必要条件

Promise 状态

promise必须是这三个状态中的一种:等待态pending,解决态fulfilled或拒绝态rejected

  1. 当promise处于pending状态的时候:
    可能变为fulfilled或者rejected状态。
  2. 当promise处于fulfilled状态的时候:
    2.1 一定不能转换为任何其它状态
    2.2 必须有一个不能改变的value
  3. 当promise处于rejected状态的时候:
    3.1 一定不能转换为任何其它状态
    3.2 必须有一个不能改变的reason

在这里,"一定不能改变"意味着不变的身份(例如 ===),但是并不意味着深度不可变性。(译注者:这里应该是说只要值的引用相同即可,并不需要引用中的每一个值都相等)

then方法

Promise必须提供一个then方法来访问当前或最终的valuereason

Promise的then方法接受俩个参数:

promise.then(onFulfilled, onRejected)
  1. onFulfilledonRejected都是可选的参数
    1.1. 如果onFulfilled不是一个函数,它必须被忽略
    1.2. 如果onRejected不是一个函数,它必须被忽略
  2. 如果onFulfilled是一个函数
    2.1. 在promise变成 fulfilled 时,应该调用 onFulfilled, 参数是value
    2.2. 在promise变成 fulfilled 之前, 不应该被调用.
    2.3. 它一定不能被调用多次。
  3. 如果onRejected是一个函数
    2.1. 在promise变成 rejected 时,应该调用 onRejected, 参数是reason
    2.2. 在promise变成 rejected 之前, 不应该被调用.
    2.3. 它一定不能被调用多次。
  4. 在执行上下文栈中只包含平台代码之前,onFulfilled或onRejected一定不能被调用
  5. 同一个promise上的then可能被调用多次
    6.1. 如果promise被解决,所有相应的onFulfilled回调必须按照他们原始调用then的顺序执行
    6.2. 如果promise被拒绝,所有相应的onRejected回调必须按照他们原始调用then的顺序执行
  6. then必须返回一个promise对象
promise2 = promise1.then(onFulfilled,onRejected)
resolvePromise
resolvePromise(promise2, x, resolve, reject)
  1. 如果promise和x引用同一个对象,用一个TypeError作为原因来拒绝promise
  2. 如果x是一个promise,根据x的状态:
    2.1. 如果x是pending,promise必须保持等待状态,直到x被解决或拒绝
    2.2. 如果x是fulfilled,用相同的value解决promise
    2.3. 如果x是rejected,用相同的reason拒绝promise
  3. 否则,如果x是一个对象或函数

    3.1.让then成为x.then

    let then = x.then.

    3.2. 如果检索属性x.then导致抛出了一个异常e,用e作为原因拒绝promise
    3.3. 如果then是一个函数,用x作为this调用它。then方法的参数为俩个回调函数,第一个参数叫做resolvePromise,第二个参数叫做rejectPromise:

     3.3.1. 如果resolvePromise用一个值y调用,运行[[Resolve]](promise, y)。译者注:这里再次调用[[Resolve]](promise,y),因为y可能还是promise
     3.3.2. 如果rejectPromise用一个原因r调用,用r拒绝promise。译者注:这里如果r为promise的话,依旧会直接reject,拒绝的原因就是promise。并不会等到promise被解决或拒绝
     3.3.3. 如果`resolvePromise`和`rejectPromise`都被调用,或者对同一个参数进行多次调用,那么第一次调用优先,以后的调用都会被忽略。译者注:这里主要针对thenable,promise的状态一旦更改就不会再改变。
     3.3.4. 如果调用then抛出了一个异常e,
         3.3.4.1 如果`resolvePromise`或`rejectPromise`已经被调用,忽略它
         3.3.4.2 否则,用`e`作为原因拒绝promise

    3.4. 如果then不是一个函数,用x解决promise

手写Promise


const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

/**
 * 1. 构建Promsie类
 * 2. 定义3种状态类型
 * 3. 设置初始状态
 * 4. resolve和reject方法
 * 5. 定义构造参数
 *  5.1 入参是一个函数, 函数接收resolve和reject两个参数.
 *  5.2 注意在初始化promise的时候, 就要执行这个函数, 并且有任何报错都要通过reject抛出去
 * 6. 实现then方法
 */
class MPromise {
  FULFILLED_CALLBACK_LIST = [];
  REJECTED_CALLBACK_LIST = [];
  _status = PENDING;

  constructor(fn) {
    // this.status = PENDING;
    this.value = null;
    this.reason = null;

    try {
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject(e.message);
    }
  }

  get status() {
    return this._status;
  }

  set status(newStatus) {
    this._status = newStatus;
    switch (newStatus) {
      case FULFILLED: {
        this.FULFILLED_CALLBACK_LIST.forEach((callback) => {
          callback(this.value);
        });
        break;
      }
      case REJECTED: {
        this.REJECTED_CALLBACK_LIST.forEach((callback) => {
          callback(this.reason);
        });
        break;
      }
    }
  }

  resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = FULFILLED;
    }
  }

  reject(reason) {
    if (this.status === PENDING) {
      this.reason = reason;
      this.status = REJECTED;
    }
  }

  then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled)
      ? onFulfilled
      : (value) => value;
    const realOnRejected = this.isFunction(onRejected)
      ? onRejected
      : (reason) => {
          throw reason;
        };
    const promise2 = new MPromise((resolve, reject) => {
      const fulfilledMicrotask = () => {
        queueMicrotask(() => {
          try {
            const x = realOnFulfilled(this.value);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      };
      const rejectedMicrotask = () => {
        queueMicrotask(() => {
          try {
            const x = realOnRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      };
      switch (this.status) {
        case FULFILLED: {
          fulfilledMicrotask();
          break;
        }
        case REJECTED: {
          rejectedMicrotask();
          break;
        }
        case PENDING: {
          this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask);
          this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask);
        }
      }
    });
    return promise2;
  }

  resolvePromise(promise2, x, resolve, reject) {
    // 如果 newPromise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 newPromise
    if (promise2 === x) {
      return reject(
        new TypeError("The promise and the return value are the same")
      );
    }

    if (x instanceof MPromise) {
      // 如果 x 为 Promise ,则使 newPromise 接受 x 的状态
      // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
      queueMicrotask(() => {
        x.then((y) => {
          this.resolvePromise(promise2, y, resolve, reject);
        }, reject);
      });
    } else if (typeof x === "object" || this.isFunction(x)) {
      // 如果 x 为对象或者函数
      if (x === null) {
        // null也会被判断为对象
        return resolve(x);
      }

      let then = null;

      try {
        // 把 x.then 赋值给 then
        then = x.then;
      } catch (error) {
        // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
        return reject(error);
      }

      // 如果then是函数
      if (this.isFunction(then)) {
        let called = false;
        try {
          then.call(
            x,
            // 如果 resolvePromise 以值 y 为参数被调用,则运行 resolvePromise
            (y) => {
              // 需要有一个变量called来保证只调用一次.
              if (called) return;
              called = true;
              this.resolvePromise(promise2, y, resolve, reject);
            },
            // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
            (r) => {
              if (called) return;
              called = true;
              reject(r);
            }
          );
        } catch (error) {
          // 如果调用 then 方法抛出了异常 e:
          if (called) return;

          // 否则以 e 为据因拒绝 promise
          reject(error);
        }
      } else {
        resolve(x);
      }
    } else {
      resolve(x);
    }
  }

  static resolve(value) {
    if (value instanceof MPromise) {
      return value;
    }

    return new MPromise((resolve) => {
      resolve(value);
    });
  }

  static reject(reason) {
    return new MPromise((resolve, reject) => {
      reject(reason);
    });
  }

  static race(promiseList) {
    return new MPromise((resolve, reject) => {
      const length = promiseList.length;

      if (length === 0) {
        return resolve();
      } else {
        for (let i = 0; i < length; i++) {
          MPromise.resolve(promiseList[i]).then(
            (value) => {
              return resolve(value);
            },
            (reason) => {
              return reject(reason);
            }
          );
        }
      }
    });
  }

  catch(callback) {
    return this.then(null, callback);
  }    

  static all(promiseList) {
    let results = [];
    let promiseCount = 0;
    let promisesLength = promiseList.length;

    return new MPromise((resolve) => {
      Promise.resolve(val).then(
        (res) => {
          promiseCount++;
          results[i] = res;
          if (promiseCount === promisesLength) {
            return resolve(results);
          }
        },
        (err) => {
          return reject(err);
        }
      );
    });
  }

  isFunction(param) {
    return typeof param === "function";
  }
}

校验Promsie是符合规范

  • 安装promises-aplus-tests

    npm i promises-aplus-tests -D
  • 暴露接口

    MPromise.defer = MPromise.deferred = function(){
    let dfd = {};
    dfd.promise = new MPromise((resolve, reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
    }
    module.exports =  MPromise
  • 运行,全部passing

    npx promises-aplus-tests .\promise.js

练习

  1. 求运行结果
const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2) // 这里有疑惑
})
promise.then(() => {
  console.log(3)
})
console.log(4)

运行结果:

1
2
4
3
  1. 求运行结果
const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!') // 这里不太确定 status
})
 
console.log('promise1', promise1)
console.log('promise2', promise2)
 
setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 2000)

运行结果

promise1 Promise { <pending> }
promise2 Promise { <pending> }
(node:50928) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: error!!!
(node:50928) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
promise1 Promise { 'success' }
promise2 Promise {
  <rejected> Error: error!!!
    at promise.then (...)
    at <anonymous> }
  1. 求运行结果
const promise = new Promise((resolve, reject) => {
  resolve('success1')
  reject('error')
  resolve('success2')
})
 
promise
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })

运行结果

then: success1
  1. 求运行结果
Promise.resolve(1)
  .then((res) => {
    console.log(res)
    return 2
  })
  .catch((err) => {
    return 3
  })
  .then((res) => {
    console.log(res)
  })

运行结果

1
2

catch如果成功执行,会返回相同的value也就是2.

  1. 求运行结果
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('once')
    resolve('success')
  }, 1000)
})
 
const start = Date.now()
promise.then((res) => {
  console.log(res, Date.now() - start)
})
promise.then((res) => {
  console.log(res, Date.now() - start)
})

运行结果

once
success 1009
success 1009
  1. 求运行结果
Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })

运行结果

then: Error: error!!!
    at Promise.resolve.then (...)
    at ...

then的onFulfilled函数里面修改rejected的:

return Promise.reject(new Error('error!!!'))
throw new Error('error!!!')
  1. 求运行结果
const promise = Promise.resolve()
  .then(() => {
    return promise
  })
promise.catch(console.error)

运行结果

TypeError: Chaining cycle detected for promise #<Promise>
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
    at Function.Module.runMain (module.js:667:11)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:607:3
  1. 求运行结果
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

运行结果

1

解释:then函数的onFulfilled不为函数,默认为(value)=>value

  1. 求运行结果
Promise.resolve()
  .then(function success (res) {
    throw new Error('error')
  }, function fail1 (e) {
    console.error('fail1: ', e)
  })
  .catch(function fail2 (e) {
    console.error('fail2: ', e)
  })

运行结果

fail2: Error: error
    at success (...)
    at ...

解释: resolve函数执行完返回fullfile状态的promsie对象。在第一个then函数的onFullfield抛出异常。则返回promsie对象的状态为rejected。被catch函数捕获。

  1. 求运行结果
process.nextTick(() => {
  console.log('nextTick')
})
Promise.resolve()
  .then(() => {
    console.log('then')
  })
setImmediate(() => {
  console.log('setImmediate')
})
console.log('end')
end
nextTick
then
setImmediate

解释: process.nextTickpromise.then 都属于 microtask,而 setImmediate 属于 macrotask,在事件循环的 check 阶段执行。事件循环的每个阶段(macrotask)之间都会执行 microtask,事件循环的开始会先执行一次 microtask。

小结

promise的实现最难理解的就是resolvePromise,非常的绕。另外学习到的了一个新的APIqueueMicrotask,有些文章是使用setTimeout来模拟实现微任务调用。

setTimeout(() => {},0)

参考文章


看见了
876 声望16 粉丝

前端开发,略懂后台;


« 上一篇
MP4规范

引用和评论

0 条评论