手写Promise

70 阅读3分钟

promise解决的问题

  1. 回调地狱
  2. 处理多个异步请求

普通逻辑

/**
 * 手写一个promise
 * promise的本质是一个状态机
 * 主要有三个状态pending,fulfilled,rejected
 * 且只有pending可以变成fulfilled、rejected
 */
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
​
class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    // 为什么使用数组,因为对象创建后,可以使用多次then方法
    // const promise = new Promise();
    // promise.then();
    // promise.then();
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
​
    const resolve = (value) => {
      //避免在executor里同时resolve、reject
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 依次将对应的函数执行
        this.onResolvedCallbacks.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) {
    // 避免异步executor
    if (this.status === PENDING){
      // 将方法先放入回调数组中 等异步完成在调用
      this.onResolvedCallbacks.push(()=>{
        onFulfilled(this.value)
      })
​
      // 将方法先放入回调数组中 等异步完成在调用
      this.onRejectedCallbacks.push(()=>{
        onRejected(this.value)
      })
​
    }
​
    if (this.status === FULFILLED){
      onFulfilled(this.value)
    }
​
    if (this.status === REJECTED){
      onRejected(this.reason)
    }
  }
}

增加then 的链式调用 和 值穿透

/**
 * promise.js
 * 手写一个promise
 * promise的本质是一个状态机
 * 主要有三个状态pending,fulfilled,rejected
 * 且只有pending可以变成fulfilled、rejected
 */
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
​
const resolvePromise = (promise, x, resolve, reject) => {
  if (promise === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  let called = false;
  if ((typeof x === 'object' && x != null) || typeof x === 'function') {
    try {
      let then = x.then;
      if (typeof then === 'function') {
        // 不要写成 x.then,直接 then.call 就可以了 因为 x.then 会再次取值,Object.defineProperty  Promise/A+ 2.3.3.3
        // 看注释1
        then.call(x, y => {
          if (called) return;
          called = true;
          // 递归解析的过程(因为可能 promise 中还有 promise) Promise/A+ 2.3.3.3.1
          resolvePromise(promise, y, resolve, reject);
        }, r => {
          if (called) return;
          called = true;
          reject(r);
        })
      } else {
        // 2.3.3.4 If then is not a function, fulfill promise with x
        resolve(x);
      }
    } catch (e) {
      // Promise/A+ 2.3.3.2
      if (called) return;
      called = true;
      reject(e)
    }
  } else {
    // 如果 x 是个普通值就直接返回 resolve 作为结果  Promise/A+ 2.3.4
    resolve(x)
  }
}
​
class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    // 为什么使用数组,因为对象创建后,可以使用多次then方法
    // const promise = new Promise();
    // promise.then();
    // promise.then();
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
​
    const resolve = (value) => {
      //避免在executor里同时resolve、reject
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 依次将对应的函数执行
        this.onResolvedCallbacks.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) {
    // Promise/A+ 允许then方法什么都不传,所以需要默认方法将值往后面的then传
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
    onRejected = typeof onRejected === 'function' ?  onRejected : err => { throw err };
​
    // 每次调用 then 都返回一个新的 promise  Promise/A+ 2.2.7
    const promise = new Promise((resolve, reject) => {
      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
          // 2.2.7.2 如果onFulfilled 或者 onRejected异常需要reject
          setTimeout(()=>{
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          },0)
        })
​
        this.onRejectedCallbacks.push(() => {
          // 2.2.7.2 如果onFulfilled 或者 onRejected异常需要reject
          setTimeout(()=>{
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          })
        })
      }
​
      if (this.status === FULFILLED) {
        setTimeout(()=>{
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        })
      }
​
      if (this.status === REJECTED) {
        setTimeout(()=>{
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        })
      }
    });
  return promise;
  }
}
​
// 增加defer与deferred使得可以测试
Promise.defer = Promise.deferred =  function () {
  let dfd = {};
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  })
  return dfd;
}
// 必须导出、和defer与deferred两个方法不然会报错
// Promise没有defer或者deferred会报错 adapter.deferred is not a function
module.exports = Promise

测试方法

npm install -g promises-aplus-tests
​
promises-aplus-tests promise/index.js

注释1

如果不使用call会产生副作用,比如

var b= 0;
var a = {
    get then(){
        b++;
        return function(){
            console.log(a);
        }
    }
};
var hello2 = a.then; // 调用1次
a.then(); // 调用2次
console.log(b); // 2
var b= 0;
var a = {
    get then(){
        b++;
        return function(){
            console.log(a);
        }
    }
};
var hello2 = a.then; // 调用1次
hello2.call(a); // 不会调用
console.log(b); // 1

参考链接

面试官:“你能手写一个 Promise 吗”

Promises/A+

为什么promise需要异步?

因为需要保证代码的一致性

promise.then(function(){ 
  if (trueOrFalse) { 
    // 同步执行 
    foo(); 
  } else { 
    // 异步执行 (如:使用第三方库)
     setTimeout(function(){ 
        foo(); 
     }) 
  } 
}); 
​
bar();

假如是同步代码那么就会受到影响

参考链接:Promise then中回调为什么是异步执行?