Promise实现原理

119 阅读9分钟

Promise产生的背景

在前端常常需要处理各种各样的异步事件,当一个异步事件的结果是另一个异步事件的条件时,就不可避免地引起回调地狱。什么是回调地狱?如下就是。这样的代码形式极不方便维护,另外也不方便进行错误处理。由此产生了Promise。

$.ajax({
  success:()=>{
    $.ajax({
      success:()=>{
        $.ajax({
          success:()=>{
            Here is code...
          }
        })
      }
    })
  }
})

Promise是什么

Promise是一个类(函数或者对象),因此通过new就可以创建一个promise。这里先简单说一下Promise的特点:
1.创建promise的时候接受一个参数,这个参数是一个函数,称为执行函数(executor),并且在创建时会立即执行。
2.每个promise都有三个状态:等待(pending)、成功(fulfilled)和失败(rejected)。
3.默认是等待态,创建时提供两个回调函数(resolve和reject)来修改promise的状态,resolve让promise变成成功态,reject让promise变成失败态,另外抛出错误也会让promise变成失败态。
4.每个promise实例都有then方法,then接受两个回调函数,分别时成功回调和失败回调
5.不管是成功还是失败,状态都只能改变一次,即只有在当前状态是等待状态时才可以改变状态。

Promise原理实现(基于Promise A+规范)

场景1:先看一种最简单的情况,执行函数直接返回成功或失败,然后调取then方法。

  let promise = new Promise((resolve,reject)=>{
    // resolve('value')  //(1)
    // reject('reason')  //(2)
    // throw new Error('error')  //(3)
  })
  promise.then(()=>{ 
    console.log('success');
  },(err)=>{
    console.log('fail');
  })
  /*此时实现起来比较简单,创建一个Promise类,在构造函数中直接调用执行函数,返回resolve和reject两个函数供用户调用,再给这个类创建一个then方法,把当前promise成功或失败的结果值返回给用户,并调用客户的回调函数。*/
  //三种状态
  const PENDING = 'PENDING' 
  const FULFILLED = 'FULFILLED'
  const REJECTED = 'REJECTED'
  class Promise{
    constructor(executor){
      this.status = PENDING;
      this.value = undefined;
      this.reason = undefined;
      let resolve = (value) => {
        if(this.status === PENDING) { //只有状态时等待时才可以更新
          this.status = FULFILLED
          this.value = value
        }
      }
      let reject = (reason) => {
        if(this.status === PENDING) {
          this.status = REJECTED
          this.reason = reason
        }
      }
      try {
        executor(resolve,reject); //直接调用执行函数
      } catch (error) { //捕获当前异常 ,异常作为失败的原因
        reject(error)
      }
    }
    then(onFulfilled,onRejected){
      if(this.status === FULFILLED){
        onFulfilled(this.value)
      }
      if(this.status === REJECTED){
        onRejected(this.reason)
      }
    }
  }

场景2:直接执行函数是一个异步函数。

  let promise = new Promise((resolve,reject)=>{
    setTimeout(() => { //如果调用了resolve 就让储存的成功回调函数执行
      resolve('value')  
    }, 1000);
  })
  promise.then(()=>{ //如果调用then的时候没有成功或者失败,就先保存两个回调
    console.log('success');
  },(err)=>{
    console.log('fail',err);
  })
  /*由于涉及到异步函数,此时如果执行then函数,当前的状态是等待态,不执行任何函数,因此需要对then方法进行修改。如果当前状态是等待态,那么就把用户的回调函数储存起来(观察者模式),待状态改变时再把储存起来的回调函数执行 */
  //三种状态
  const PENDING = 'PENDING' 
  const FULFILLED = 'FULFILLED'
  const REJECTED = 'REJECTED'
  class Promise{
    constructor(executor){
      this.status = PENDING;
      this.value = undefined;
      this.reason = undefined;
      this.onResolveCallbacks = []  //存放成功时的回调
      this.onRejectCallbacks = [] //存放失败时的回调
      let resolve = (value) => {
        if(this.status === PENDING) { //只有状态时等待时才可以更新
          this.status = FULFILLED
          this.value = value
          this.onResolveCallbacks.forEach(fn=>fn()) //发布过程
        }
      }
      let reject = (reason) => {
        if(this.status === PENDING) {
          this.status = REJECTED
          this.reason = reason
          this.onRejectCallbacks.forEach(fn=>fn())
        }
      }
      try {
        executor(resolve,reject); //直接调用执行函数
      } catch (error) { //捕获当前异常 ,异常作为失败的原因
        reject(error)
      }
    }
    then(onFulfilled,onRejected){
      if(this.status === FULFILLED){
        onFulfilled(this.value)
      }
      if(this.status === REJECTED){
        onRejected(this.reason)
      }
      if(this.status === PENDING){
        this.onResolveCallbacks.push(()=>{
          onFulfilled(this.value)
        })
        this.onRejectCallbacks.push(()=>{
          onRejected(this.reason)
        })
      }
  }

场景3:根据Promise A+规范,当返回的值是promise,就继续执行它的then方法;当返回普通值(包括undefined,即不写),返回的值作为下一次then的成功回调

let promise = new Promise((resolve,reject)=>{
  resolve('hello');
})

let promise2 = promise.then((data)=>{
  // return data //如何将data向下传递:调用promise2的resolve方法
  return new Promise((resolve,reject)=>{
    resolve(new Promise((resolve,reject)=>{
      setTimeout(() => {
        resolve('hello');
      }, 1000);
    }));
  })
  // return promise2
})
promise2.then(data=>{
  console.log('success:'+data)
},err=>{
    console.log('err:'+err)
})
/*此时情况就变得有些复杂,我们必须先去判断第一个then方法的调用返回的是什么,根据返回结果的类型,再决定resolve或reject对应的值。我们这里先假设回调的是resolve。
*/
let x = onFulfilled(this.value)
resolvePromise(promise2,x,resolve,reject)//根据回调函数的结果调用一个函数来决定返回值

//resolvePromise函数的实现
const resolvePromise = (promise2,x,resolve,reject )=>{
  //判断 可能你的promise要和别人的promise来混用
  //可能不同的promise库之间要相互调用
  if(promise2 === x) {//如果x和promise2是同一个,则永远不会成功,直接抛出错误
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  //---判断x的状态 判断x是不是promise---
  //先判断是不是对象(原生的是对象)或者函数(别人写的有可能是函数)
  if((typeof x === 'object' && x!==null) || typeof x === 'function'){
    let called; //为了考虑别人的promise不健壮 状态只能改变一次
    try{
      let then = x.then;//取出then方法 这个then方法采用defineProperty来定义的
      if(typeof then === 'function'){
        //判断then是不是一个函数,如果不是函数  说明不是promise
        //只能是promise
        then.call(x,y=>{//如果x是一个promise就采用这个promise的返回结果
          if(called) return;
          called = true
          resolvePromise(promise2,y,resolve,reject)
        },r=>{
          if(called) return;
          called = true
          reject(r) //直接用r作为失败的结果
        })
        //为什么不用x.then(()=>{},()=>{}) 这样会再一次取then方法
      }else {
        resolve(x) //比如:x={then:123}
      }
    }catch(e){
      if(called) return;
      called = true
      reject(e); //取then失败 直接触发promise2的失败逻辑
    }
    
  }else {
    //肯定不是promise
    resolve(x);// 是普通值 直接成功即可
  }
}

场景4:catch和finally只是promise的两个语法糖,实现起来比较容易

catch(errCallback) {//没有成功的then  语法糖
  return this.then(null,errCallback)
}
finally(Callback) {
  Callback()
  return this.then()
}

场景5:此外再实现Promise的all和race方法

//判断是否是promise
const isPromise = value =>{
  if((typeof value === 'object' && value !== null) || typeof value === 'function'){
    return typeof value.then === 'function'
  }
  return false;
}

//promise.all方法
Promise.all = function (promises) {
  return new Promise((resolve,reject)=>{
    let arr = [];//返回的数组
    let  i = 0;
    let processData = (index,data)=>{
      arr[index]=data;
      if(++i === promises.length){
        resolve(arr)
      }
    }
    for(let i=0;i<promises.length;i++){
      let current = promises[i];
      if (isPromise(current)){
        current.then(data=>{
          processData(i,data)
        },reject)
      }else{
        processData(i,current)
      }
    }
  })
}

// Promise.race方法
Promise.race = function (promises) {
  return new Promise((resolve,reject)=>{
    let arr=[];
    let firstIndex = true;
    let firstBack;
    let i=0;
    let processData = (index,data)=>{
      arr[index]=data;
      if (firstIndex) {
        firstBack = data
        firstIndex = false
      }
      if(++i === promises.length){
        resolve(firstBack)
      }
    }
    for(let i=0;i<promises.length;i++){
      let current = promises[i];
      if(isPromise(current)){
        current.then(data=>{
          processData(i,data)
        },reject)
      }else{
        processData(i,current)
      }
    }
  })
}

Promise的完整实现

Promise的完整实现,全部代码

console.log('------------基于Promise A+规范的promise实现------------');
//宏 三种状态
const PENDING = 'PENDING' 
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
const resolvePromise = (promise2,x,resolve,reject )=>{
  //判断 可能你的promise要和别人的promise来混用
  //可能不同的promise库之间要相互调用
  if(promise2 === x) {//如果x和promise2是同一个,则永远不会成功,直接抛出错误
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  //---判断x的状态 判断x是不是promise---
  //先判断是不是对象(原生的是对象)或者函数(别人写的有可能是函数)
  if((typeof x === 'object' && x!==null) || typeof x === 'function'){
    let called; //为了考虑别人的promise不健壮 状态只能改变一次
    try{
      let then = x.then;//取出then方法 这个then方法采用defineProperty来定义的
      if(typeof then === 'function'){
        //判断then是不是一个函数,如果不是函数  说明不是promise
        //只能是promise
        then.call(x,y=>{//如果x是一个promise就采用这个promise的返回结果
          if(called) return;
          called = true
          resolvePromise(promise2,y,resolve,reject)
        },r=>{
          if(called) return;
          called = true
          reject(r) //直接用r作为失败的结果
        })
        //为什么不用x.then(()=>{},()=>{}) 这样会再一次取then方法
      }else {
        resolve(x) //比如:x={then:123}
      }
    }catch(e){
      if(called) return;
      called = true
      reject(e); //取then失败 直接触发promise2的失败逻辑
    }
    
  }else {
    //肯定不是promise
    resolve(x);// 是普通值 直接成功即可
  }
}
class Promise{
  constructor(executor){
    this.status = PENDING //默认等待
    this.value = undefined
    this.reason = undefined
    this.onResolveCallbacks = []  //存放成功时的回调
    this.onRejectCallbacks = [] //存放失败时的回调
    let resolve = (value) =>{    
      if(value instanceof Promise){ //resolve一个promise的情况
        return value.then(resolve,reject)
      }
      if(this.status === PENDING) { //只有状态是等待时才可以更新
        this.status = FULFILLED
        this.value = value
        this.onResolveCallbacks.forEach(fn=>fn()) //发布过程
      }
    }
    
    let reject = (reason) =>{
      if(this.status === PENDING) {
        this.status = REJECTED
        this.reason = reason
        this.onRejectCallbacks.forEach(fn=>fn())
      }
    }
    //executor执行时传入两个参数 给用户改变状态
    try {  //try+catch只能捕获同步异常
      executor(resolve,reject);
    } catch (error) { //捕获当前异常 ,异常作为失败的原因
      reject(error)
    }
  }
  //只要x是普通值就会让下一个promise变成成功态
  //这个x有可能是一个promise  采用这个promise的状态
  then(onFulfilled,onRejected){
    //穿透
    onFulfilled = typeof onFulfilled === 'function'?onFulfilled:val=>val
    onRejected = typeof onRejected === 'function'?onRejected:err=>{throw err}
    //递归
    let promise2 = new Promise((resolve,reject)=>{
      if(this.status === FULFILLED){
        setTimeout(() => {
          try{
            let x = onFulfilled(this.value)
            resolvePromise(promise2,x,resolve,reject)
          }catch(e){
            reject(e)
          }
        }, 0);
      }
      if(this.status === REJECTED){
        setTimeout(() => {
          try{  
            let x = onRejected(this.reason)
            resolvePromise(promise2,x,resolve,reject)
          }catch(e){
            reject(e)
          }
        }, 0);
      }
      if(this.status === PENDING){
        this.onResolveCallbacks.push(()=>{
          setTimeout(() => {
            try{  
              let x = onFulfilled(this.value)
              resolvePromise(promise2,x,resolve,reject)
            }catch(e){
              reject(e)
            }
          }, 0);
        })
        this.onRejectCallbacks.push(()=>{
          setTimeout(() => {
            try{  
              let x = onRejected(this.reason)
              resolvePromise(promise2,x,resolve,reject)
            }catch(e){
              reject(e)
            }
          }, 0);
        })
      }
    })   
    return promise2;
  }
  catch(errCallback) {//没有成功的then  语法糖
    return this.then(null,errCallback)
  }
  finally(Callback) {
    Callback()
    return this.then()
  }
}

Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve,reject)=>{
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

//判断是否是promise
const isPromise = value =>{
  if((typeof value === 'object' && value !== null) || typeof value === 'function'){
    return typeof value.then === 'function'
  }
  return false;
}

//手写promise.all方法
Promise.all = function (promises) {
  return new Promise((resolve,reject)=>{
    let arr = [];//返回的数组
    let  i = 0;
    let processData = (index,data)=>{
      arr[index]=data;
      if(++i === promises.length){
        resolve(arr)
      }
    }
    for(let i=0;i<promises.length;i++){
      let current = promises[i];
      if (isPromise(current)){
        current.then(data=>{
          processData(i,data)
        },reject)
      }else{
        processData(i,current)
      }
    }
  })
}

// 手写Promise.race方法
Promise.race = function (promises) {
  return new Promise((resolve,reject)=>{
    let arr=[];
    let firstIndex = true;
    let firstBack;
    let i=0;
    let processData = (index,data)=>{
      arr[index]=data;
      if (firstIndex) {
        firstBack = data
        firstIndex = false
      }
      if(++i === promises.length){
        resolve(firstBack)
      }
    }
    for(let i=0;i<promises.length;i++){
      let current = promises[i];
      if(isPromise(current)){
        current.then(data=>{
          processData(i,data)
        },reject)
      }else{
        processData(i,current)
      }
    }
  })
}

module.exports = Promise

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