阅读 95

手写Promise A+ 规范

前言

基本每个学习前端的同学,都有一段必定要走的路,其中Promise 算是接触的比较早的一个异步解决方案,而且,不管是日常开发或者是面试中必定都经历过他,而平时也都是自己在使用,也没有深入了解原理,今天就自己实现一个Promise A+ 规范;

本文需要的知识点

  • eventLoop(事件循环)
  • 柯里化函数
  • this指向
  • 原型链
  • ES6

铺垫

异步的解决方案

  • callback回调函数 ----- 在node.js中体现尤为明显
  • generator函数 ------- saga
  • Promise ----------------- 主角
  • async await ----- 日常使用

今天咱们主角是 Promise,咱们的Promise到底解决了什么问题呢?

  1. Promise的出现解决了 回调地狱 的问题,给大家贴张截图看下回调地狱

image.png

  1. 解决了回调地狱的同时,也解决了代码的可读性,可维护性问题;

Promise有什么特性吗?

首先 他有一个.then() 方法,其次 有三个状态 pending fulfilled rejected 三个状态,一旦进入某个状态后不可更改。而且还支持链式调用。

下面咱们看一下简单的异步代码:

    // 异步代码的定义
    var asyncFun = function(callback){
        setTimeout(function(){
            var randomMath = Math.random();
            if(randomMath > 0.5){
                callback(null,randomMath)
            }else {
                callback('数组小于0.5')
            }
        },1000)
    }
    
    asyncFun((err,res) => {
        if(err){
            console.log('err',err);
            return;
        }
        console.log('res',res)
    })
复制代码

这段代码是在node中很常见,属于异步的回调函数,而回调函数的第一位是error,也算是一个默认的 '潜规则' 吧。

理想中Promise中就可以变成如下

   new Prmise((resolve,reject)=>{
    resolve('我要进行reslove')
   }).then(res => {
    console.log('res',res)
   },err => {
    console.log('err',err)
   })
复制代码

实现大体Promise框架

   class MyPromise {

    constructor(fn) {
    // 在这里可以在做下判断不是function的判断
        this.status = 'pending';
        this.value = '';
        this.reason = '';

        this.resolve = this.resolve.bind(this); // 这个绑定this的代码可以抽成函数
        this.reject = this.reject.bind(this);
        fn(this.resolve, this.reject); // 在初始化的时候将 resolve reject函数放进去
    }


    resolve(value) {
        if (this.status === MyPromise.PENDING) { // 当resolve时,只有状态符合 pending的时候
            this.status = MyPromise.FULFILLED;
            this.value = value; // 将值赋给res
            console.log('value',value)

        }
    }

    reject(value) {
        if (this.status === MyPromise.PENDING) { // 这边同 resolve一样的判断
            this.status = MyPromise.REJECTED;
            this.reason = value;
        }
    }

}

MyPromise.PENDING = 'pending';
MyPromise.FULFILLED = 'fulfilled';
MyPromise.REJECTED = 'rejected';

let p = new MyPromise((resolve, reject) => {
    resolve('我成功reslove了啊哈哈哈');
    // reject('失败');
})
复制代码

OK,到这,一个大致的Promise基本小框架就搭建完成了,主要的思想就是将函数当做参数传递,利用ES6的class类接收一个变量,这个变量的参数,在class里面提前定义好;
当然咱们一定要少用这些魔法字符串,可增加可维护性。

实现 基本(最简单) .then 回调功能;

class MyPromise {
  constructor(fn) {
    // 在这里可以在做下判断不是function的判断
    this.status = 'pending';
    this.value = '';
    this.reason = '';

    this.resolve = this.resolve.bind(this); // 这个绑定this的代码可以抽成函数
    this.reject = this.reject.bind(this);
    // 增加这个try catch 防止用户在resolve的直接报error
    try{
       fn(this.resolve, this.reject); // 在初始化的时候将 resolve reject函数放进去
    }catch(e){
       this.reject(e) 
    }
  }


  resolve(value) {
    if (this.status === MyPromise.PENDING) { // 当resolve时,只有状态符合 pending的时候
      this.status = MyPromise.FULFILLED;
      this.value = value; // 将值赋给res
    }
  }

  reject(value) {
    if (this.status === MyPromise.PENDING) { // 这边同 resolve一样的判断
      this.status = MyPromise.REJECTED;
      this.reason = value;
    }
  }
  // 最简单的then回调
  then(onFulfilled, onRejected) {
    if (typeof onFulfilled !== 'function') {
      onFulfilled = function (value) {
        return value
      };
    }
    if (typeof onRejected !== 'function') {
      onRejected = function (reason) {
        throw reason;
      }
    }
    if (this.status === MyPromise.FULFILLED) {
      setTimeout(() => {
        onFulfilled(this.value)
      })
    }

    if (this.status === MyPromise.REJECTED) {
      setTimeout(() => {
        onRejected(this.reason)
      })
    }
  }
}

MyPromise.PENDING = 'pending';
MyPromise.FULFILLED = 'fulfilled';
MyPromise.REJECTED = 'rejected';

let p = new MyPromise((resolve, reject) => {
  // throw new Error("看我newError位置")
  resolve('我成功reslove了啊哈哈哈');
  // reject('失败');
}).then((res) => {
  console.log('res', res)
},err => {
  console.log('err', err)
})
复制代码

截止到目前这个同步的 .then 功能实现了,如果将 resolve 用setTimeout包裹住,这时候就会发现MyPromise 中的状态不对,一直是pending状态,而且打印不出来任何console;是因为eventLoop,setTimeout会进入异步队列,而这时我们的this.status的状态还是pending,所以我们定义数组来接收他;同时在resolve or reject中遍历,OK,按照这个思路咱们来写一下;

Promise异步处理

class MyPromise {
  constructor(fn) {
    // 在这里可以在做下判断不是function的判断
    this.status = 'pending';
    this.value = '';
    this.reason = '';

    this.resolve = this.resolve.bind(this); // 这个绑定this的代码可以抽成函数
    this.reject = this.reject.bind(this);
    this.onFulfilledCallback = [];
    this.onRejectedCallback = [];
    // 增加这个try catch 防止用户在resolve的直接报error
    try {
      fn(this.resolve, this.reject); // 在初始化的时候将 resolve reject函数放进去
    } catch (e) {
      this.reject(e)
    }
  }


  resolve(value) {
    if (this.status === MyPromise.PENDING) { // 当resolve时,只有状态符合 pending的时候
      this.status = MyPromise.FULFILLED;
      this.value = value; // 将值赋给res

      this.onFulfilledCallback.forEach((fn) => fn(this.value));
    }
  }

  reject(value) {
    if (this.status === MyPromise.PENDING) { // 这边同 resolve一样的判断
      this.status = MyPromise.REJECTED;
      this.reason = value;

      this.onRejectedCallback.forEach((fn) => fn(this.reason));
    }
  }
  // 最简单的then回调
  then(onFulfilled, onRejected) {
    if (typeof onFulfilled !== 'function') {
      onFulfilled = function (value) {
        return value
      };
    }
    if (typeof onRejected !== 'function') {
      onRejected = function (reason) {
        throw reason;
      }
    }
    if (this.status === MyPromise.FULFILLED) {
      setTimeout(() => {
        onFulfilled(this.value)
      })
    }

    if (this.status === MyPromise.REJECTED) {
      setTimeout(() => {
        onRejected(this.reason)
      })
    }

    if (this.status === MyPromise.PENDING) {
      this.onFulfilledCallback.push(val => {
        setTimeout(() => {
          onFulfilled(val)
        })
      })

      this.onRejectedCallback.push(reason => {
        setTimeout(() => {
          onRejected(reason)
        })
      })
    }
  }
}

MyPromise.PENDING = 'pending';
MyPromise.FULFILLED = 'fulfilled';
MyPromise.REJECTED = 'rejected';

let p = new MyPromise((resolve, reject) => {
  // throw new Error("看我newError位置")
  setTimeout(() => {
    resolve('我成功reslove了啊哈哈哈');
  })
  // reject('失败');
}).then((res) => {
  console.log('res', res)
}, err => {
  console.log('err', err)
})

复制代码

咱们这个想法正好可以将 异步 这个问题解决掉,异步解决掉了,众所周知,promise最大的特点是链式调用,而且一旦进入某个状态就不可更改了,就得改变then 后面得值,这样的话必须通过新的实例,而且每次 then回调需要防止报错,所以每个都需要加上try catch防止报错,咱们来试试怎么实现;

实现Promise 简单链式调用


class MyPromise {
  constructor(fn) {
    // 在这里可以在做下判断不是function的判断
    this.status = MyPromise.PENDING;
    this.value = '';
    this.reason = '';

    this.resolve = this.resolve.bind(this); // 这个绑定this的代码可以抽成函数
    this.reject = this.reject.bind(this);
    this.onFulfilledCallback = [];
    this.onRejectedCallback = [];
    // 增加这个try catch 防止用户在resolve的直接报error
    try {
      fn(this.resolve, this.reject); // 在初始化的时候将 resolve reject函数放进去
    } catch (e) {
      this.reject(e)
    }
  }


  resolve(value) {
    if (this.status === MyPromise.PENDING) { // 当resolve时,只有状态符合 pending的时候
      this.status = MyPromise.FULFILLED;
      this.value = value; // 将值赋给res
      this.onFulfilledCallback.forEach((fn) => fn(this.value));
    }
  }

  reject(value) {
    if (this.status === MyPromise.PENDING) { // 这边同 resolve一样的判断
      this.status = MyPromise.REJECTED;
      this.reason = value;
      this.onRejectedCallback.forEach((fn) => fn(this.reason));
    }
  }

  // 最简单的then回调
  then(onFulfilled, onRejected) {
    if (typeof onFulfilled !== 'function') {
      onFulfilled = function (value) {
        return value
      };
    }
    if (typeof onRejected !== 'function') {
      onRejected = function (reason) {
        throw reason;
      }
    }
    // 这边是返回了一个新的promise对象,可以实现简单链式调用
    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === MyPromise.FULFILLED) {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolve(x);
          } catch (e) {
            console.log('3',e)
            reject(e);
          }
        })
      }
      // 加 setTimeout的作用防止resolve的时候直接跳过
      if (this.status === MyPromise.REJECTED) {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolve(x);
          } catch (e) {
            reject(e);
          }
        })
      }

      if (this.status === MyPromise.PENDING) {
        this.onFulfilledCallback.push(val => {
          setTimeout(() => {
            try {
              const x = onFulfilled(val);
              resolve(x);
            } catch (e) {
              reject(e);
            }
          })
        })

        this.onRejectedCallback.push(reason => {
          setTimeout(() => {
            try {
              const x = onRejected(reason);
              resolve(x);
            } catch (e) {
              reject(e);
            }
          })
        })
      }
    });
    return promise2;
  }
}

MyPromise.PENDING = 'pending';
MyPromise.FULFILLED = 'fulfilled';
MyPromise.REJECTED = 'rejected';

let p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('我成功reslove了啊哈哈哈');
  })
}).then((res) => {
  return '1111' + res
}, err => {
  console.log('err', err)
}).then(res => {
  console.log('000', res)
}, err => {
  console.log('err222',err)
})

复制代码

现在简单的链式调用已经完成了,还有一个问题是,如果 Promise.then 中用户直接return 出来了一个Promise 那么这样的话就不太行了,那这样的话咱们定义一个函数 resolvePromise ,专门处理这个逻辑;

实现Promise终极链式调用

class MyPromise {
  constructor(fn) {
    // 在这里可以在做下判断不是function的判断
    this.status = MyPromise.PENDING;
    this.value = '';
    this.reason = '';

    this.resolve = this.resolve.bind(this); // 这个绑定this的代码可以抽成函数
    this.reject = this.reject.bind(this);
    this.onFulfilledCallback = [];
    this.onRejectedCallback = [];
    // 增加这个try catch 防止用户在resolve的直接报error
    try {
      fn(this.resolve, this.reject); // 在初始化的时候将 resolve reject函数放进去
    } catch (e) {
      this.reject(e)
    }
  }


  resolve(value) {
    if (this.status === MyPromise.PENDING) { // 当resolve时,只有状态符合 pending的时候
      this.status = MyPromise.FULFILLED;
      this.value = value; // 将值赋给res
      this.onFulfilledCallback.forEach((fn) => fn(this.value));
    }
  }

  reject(value) {
    if (this.status === MyPromise.PENDING) { // 这边同 resolve一样的判断
      this.status = MyPromise.REJECTED;
      this.reason = value;
      this.onRejectedCallback.forEach((fn) => fn(this.reason));
    }
  }

  // 最简单的then回调
  then(onFulfilled, onRejected) {
    if (typeof onFulfilled !== 'function') {
      onFulfilled = function (value) {
        return value
      };
    }
    if (typeof onRejected !== 'function') {
      onRejected = function (reason) {
        throw reason;
      }
    }
    // 这边是返回了一个新的promise对象,可以实现简单链式调用
    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === MyPromise.FULFILLED) {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            MyPromise.resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            console.log('3', e)
            reject(e);
          }
        })
      }
      // 加 setTimeout的作用防止resolve的时候直接跳过
      if (this.status === MyPromise.REJECTED) {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            MyPromise.resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        })
      }

      if (this.status === MyPromise.PENDING) {
        this.onFulfilledCallback.push(val => {
          setTimeout(() => {
            try {
              const x = onFulfilled(val);
              resolve(x);
            } catch (e) {
              reject(e);
            }
          })
        })

        this.onRejectedCallback.push(reason => {
          setTimeout(() => {
            try {
              const x = onRejected(reason);
              resolve(x);
            } catch (e) {
              reject(e);
            }
          })
        })
      }
    });
    return promise2;
  }
}

MyPromise.PENDING = 'pending';
MyPromise.FULFILLED = 'fulfilled';
MyPromise.REJECTED = 'rejected';

MyPromise.resolvePromise = function (promise2, x, resolve, reject) {
  // 如果promise2 和 x 相等
  if (promise2 === x) {
    reject(new TypeError('Chaining cycle detected for promise'));

  }
  // 为了防止多次调用,如果多次调用,采用第一次的值!
  let isCalled = false;
  if (x instanceof MyPromise) {
    // 如果为promise 直接 回调
    x.then(
      // res => resolve(res),
      res => MyPromise.resolvePromise(promise2, res, resolve, reject),
      reason => reject(reason)
    );

    // 这个if有坑,大家一定得明白下 typeOf null 也等于 Object
  } else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    // 这边照例try catch
    try {
      const then = x.then; // 这边测试的时候很奇怪,为什么要这么写.....有大佬明白的可以指出
      if (typeof then === 'function') {
        then.call(x,
          res => {
            if (isCalled) return;
            isCalled = true;
            MyPromise.resolvePromise(promise2, res, resolve, reject)
          },
          reason => reject(reason)
        )
      } else {
        if (isCalled) return;
        isCalled = true;
        resolve(x)
      }
    } catch (e) {
      if (isCalled) return;
      isCalled = true;
      reject(e)
    }

  } else {
    // 等到他只是值得时候进行resolve
    resolve(x);
  }
}


let p = new MyPromise((resolve, reject) => {
  resolve('我成功reslove了啊哈哈哈');
}).then((res) => {
  return new MyPromise((resolve, reject) => {
    resolve(11111)
  })
}, err => {
  console.log('err', err)
}).then(res => {
  console.log('000', res)
}, err => {
  console.log('err222', err)
})


复制代码

总结

现在大致的符合Promise A+ 规范 就实现完成了,大家也可以通过一些插件来测试咱们写的Promise是否符合规范。本文实现的比较简陋,后续抽空会继续更新;感谢支持

欢迎大神指点斧正

本文借鉴文章:
图灵社区 www.ituring.com.cn/article/665…

文章分类
前端
文章标签