从“手写Promise”中可以学到什么?

从面试角度出发,可能我们会经常面临这几个问题:

  • Promise解决了什么问题?
  • Promise的业界实现都有哪些?
  • Promise常用的API有哪些?
  • 能不能手写一个符合规范的Promise?
  • Promise在事件循环中的执行过程是怎样的?
  • Promise有什么缺陷?能否解决?

1. Promise出现的原因

在Promise出现前,传统的异步编程中,处理异步请求时需要通过回调函数来完成,如果异步请求依赖其他异步请求,则代码需要通过层层嵌套会调的方式满足这种依赖,如果嵌套层数过多,可读性和可维护性都会变得很差,这时就会产生所谓的「回调地狱」,产生回调地狱的原因有两点:

  • 嵌套调用:第一个函数的输出往往是第二个函数的输入
  • 并发地处理多个异步请求

2.回顾Promise的使用方式

const p1 = new Promise((resolve, reject) => {
  console.log('create a promise')
  resolve('成功了')
})
console.log('after new promise')
const p2 = p1.then(data => {
  console.log(data)
  throw new Error('失败了')
})
const p3 = p2.then(data => {
  console.log('success', data)
}, err => {
  console.log('faild', err)
})
// create a promise
// after new promise
// 成功了
// faild Error: 失败了
  • 首先在新建Promise对象时,需要传入一个成功的回调函数和一个失败的回调函数,Promise的主要业务流程都在这里执行。
  • Promise的状态不可逆且唯一。

3.勾勒初级的Promise

通过回顾Promise的使用,可知Promise是一个构造函数,这个构造函数可以构造Promise实例。Promise只传一个参数,按照Promise/A+规范的命名, 这个参数叫做“executor”。executor会提供给构造者两个方法,一个是resolve,一个是reject。

简单剖析一下Promise的实质:Promise构造函数返回一个Promise对象实例,这个返回的Promise对象有一个then方法。在then方法中,调用者可以定义两个函数类型的参数,分别是onfulfilled和onrejected。其中onfulfilled通过参数可以获取Promise对象经过resolve处理过的值,onrejected可以获取Promise对象经过reject处理过的值。

在已有Promise构造函数的基础上加上then原型方法的骨架:

function Promise(executor) {
  
}
Promise.prototype.then = function(onfulfilled, onrejected) {

}

下面先看一个示例,从示例中理解Promise的重点内容:

let promise1 = new Promise((resolve, reject) => {
  resolve('data')
})
promise1.then(data => {
  console.log(data)
})
let promise2 = new Promise((resolve, reject) => {
  reject('error')
})
promise2.then(data => {
  console.log(data)
},error => {
  console.log(error.)
})

在使用new关键字调用Promise时,在合适的时机(往往是异步操作结束时)调用executor的参数resolve,并将经过resolve处理后的值作为resolve的函数参数执行,这个值可以在then方法的第一个参数(onfulfilled)中拿到;同理reject中的参数可以在onrejcted中拿到。

因此在实现Promise时,需要有两个变量来存储经过resolve处理过的值以及经过reject处理过的值。同时还需要存在一个状态,这个状态就是Promise实例的状态(pending, fulfilled, rejected)。最后需要提供resolve以及reject方法,这两个方法需要作为executor的参数提供给开发者使用。

function Promise(executor) {
  this.status =  'pending'
  this.value = null
  this.reason = null
  
  const resolve = (value) => {
    if (this.status === 'pending') {
      this.value = value
      this.status = 'fulfilled'
     }
  }
  const reject = (value) =>  {
    if (this.status === 'pending') {
      this.value = value
      this.status = 'rejected'
     }
  }
  executor(resolve, reject)
}
Promise.prototype.then = function (onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
  if(this.status === 'fulfilled') {
    onfulfilled(this.value)
  }
  if(this.status === 'rejected') {
    onrejected(this.reason)
  }
}

为保证onfulfilled、onrejected能够健壮地执行,我们为其设置了默认值,其默认值为一个函数元。

then放在Promise的构造函数的原型上是因为每个Promise实例的then方法逻辑都是一样的,实例在调用该方法时,可以通过原型来调用,而不需要每次实例化都新建一个then方法,以便节省内存。

4.Promise异步实现完善

通过下面示例的代码来分析目前我们的实现差了哪些内容:

let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('data')
  }, 2000)
})
promise.then(data => {
  console.log(data)
})

正常来讲,上述代码应该在2s后输出data,但现在代码没有输出任何信息,因为上面已实现的逻辑全是同步的。上述代码在setTimeout中调用resolve,也就是在2秒后才会执行resolve方法,更改Promise实例的状态。

所以我们应该在什么时间去调用onfulfilled方法?

我们应该在合适的时间去调用onfulfilled方法,这个合适的时间应该是开发者调用resolve的时刻,我们可以先在状态为pending时把开发者传进来的onfulfilled方法存起来,再在resolve方法中执行即可。

function Promise(executor) {
  this.status =  'pending'
  this.value = null
  this.reason = null
  
  this.onFulfilledFunc = Function.prototype
  this.onRejectedFunc = Function.prototype
  
  const resolve = (value) => {
    if (this.status === 'pending') {
      this.value = value
      this.status = 'fulfilled'
  
      this.onFulfilledFunc(this.value)
     }
  }
  const reject = (value) =>  {
    if (this.status === 'pending') {
      this.value = value
      this.status = 'rejected'

      this.onRejectedFunc(this.reason)
     }
  }
  executor(resolve, reject)
}
Promise.prototype.then = function (onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
  if(this.status === 'fulfilled') {
    onfulfilled(this.value)
  }
  if(this.status === 'rejected') {
    onrejected(this.reason)
  }
  if(this.status === 'pending') {
    this.onFulfilledFunc = onfulfilled
    this.onRejectedFunc = onrejected
  }
}

通过测试发现,上述Promise实现了异步,接着看下面的一个例子,判断以下代码的输出结果:

let promise = new Promise((resolve, reject) => {
  resolve('data')
})
promise.then(data => {
  console.log(data)
})
console.log(1)

正常顺序是先输出1,再输出data。
而目前实现的Promise代码没有考虑这种情况,因此需要将resolve和reject的执行放到任务队列中。暂时先放到setTimeout中,保证异步执行。

  const resolve = (value) => {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'
  
        this.onFulfilledFunc(this.value)
     }
    })
  }
  const reject = (value) =>  {
    setTimeout(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'rejected'

        this.onRejectedFunc(this.reason)
       }
    })
  }
  executor(resolve, reject)

5.Promise细节完善

5.1在Promise实例状态变更前添加多个then方法,比如:

let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('data')
  }, 2000)
})
promise.then(data => {
  console.log(`1: ${data}`)
})
promise.then(data => {
  console.log(`2: ${data}`)
})
// 以上代码应该输出:
// 1:data
// 2:data

而我们的实现只会输出2:data,这是因为第二个then中的onFulfilledFunc会覆盖第一个then方法中的onFulfilledFunc。这个问题可以通过将所有then方法中的onFulfilledFunc储存到一个数组里,在当前Promise被决议时依次执行onFulfilledArray数组内的方法即可。对于onRejectedFunc同理。

function Promise(executor) {
  this.status =  'pending'
  this.value = null
  this.reason = null
  
  this.onFulfilledArray = []
  this.onRejectedArray = []
  
  const resolve = (value) => {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'
  
        this.onFulfilledArray.forEach(func => {
          func(value)
        })
       }
    })
  }
  const reject = (value) =>  {
    setTimeout(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'rejected'

        this.onRejectedArray.forEach(func => {
          func(value)
        })
       }
    })
  }
  executor(resolve, reject)
}
Promise.prototype.then = function (onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
  if(this.status === 'fulfilled') {
    onfulfilled(this.value)
  }
  if(this.status === 'rejected') {
    onrejected(this.reason)
  }
  if(this.status === 'pending') {
    this.onFulfilledArray.push(onfulfilled)
    this.onRejectedArray.push(onrejected)
  }
}

5.2处理构造函数出错的情况

在构造函数中如果出错,将自动触发Promise实例状态变为reject,因此可以用时try...catch对executor进行包裹。

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

通过以上的实现,可以对Promise总结出一些结论:

  • Promise的状态具有凝固性
  • Promise可以在Then方法第二个参数重进行错误处理
  • Promise实例可以添加多个then处理场景

6. 实现Promise的链式调用

通过两个例子来观察Promise的链式调用:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('lucas')
  }, 2000)
})
promise.then(data => {
  console.log(data)
  return `${data} next then`
}).then(data => {
  console.log(data)
})
// 2s后输出lucas
// 紧接着输出 lucas next then

如果在第一个then方法体中的onfulfilled函数中返回另一个Promise实例,那会是什么结果?

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('lucas')
  }, 2000)
})
promise.then(data => {
  console.log(data)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(`${data} next then`)
    }, 4000)
  })
}).then(data => {
  console.log(data)
})
// 2s后输出lucas
// 4s后输出 lucas next then

由此可知,一个Promise实例then方法的onfulfilled函数和onrejected函数是支持再次返回一个Promise实例的,也支持返回一个非Promise实例的普通值。并且返回的这个Promise实例或普通值会传给下一个then方法的onfulfilled函数或onrejected函数。

6.1 链式调用的初步实现

首先分析,为了支持then方法的链式调用,每一个then方法的onfulfilled函数和onrejected函数是不是都应该返回一个Promise实例?

Promise.prototype.then = function (onfulfilled, onrejected) {
  let promise2
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
  if(this.status === 'fulfilled') {
    return promise2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        try{
          // 将onfulfilled处理后的结果赋给新的promise2的resolve
          let result = onfulfilled(this.value)
          resolve(result)
        } catch(e) {
          reject(e)
        }
      })
    })
  }
  if(this.status === 'rejected') {
    return promise2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        try{
          // 将onrejected处理后的结果赋给新的promise2的resolve
          let result = onrejected(this.value)
          resolve(result)
        } catch(e) {
          reject(e)
        }
      })
    })
  }
  if(this.status === 'pending') {
    return promise2 = new Promise((resolve, reject) => {
      this.onFulfilledArray.push(() => {
        try{
          let result = onfulfilled(this.value)
          resolve(result)
        } catch(e) {
          reject(e)
        }
      })
      this.onRejectedArray.push(() => {
        try{
          let result = onrejected(this.reason)
          resolve(result)
        } catch(e) {
          reject(e)
        }
      })
    })
  }
}

这里要重点理解this.status === 'pending'判断分支里的逻辑。之前是将onfulfilled/onrejected存储在数组中,等异步结束后依次执行数组里的函数;现在是将一个新的函数存在数组中,等异步结束后一次执行数组里的函数。
以上代码只实现了onfulfilled/onrejected中显式返回一个普通值的链式调用,之后再完善一下显式返回Promise实例的链式调用。

6.2 完善显式返回Promise实例的链式调用

基于第一种onfulfilled和onrejected函数返回一个普通值的情况,要实现这种onfulfilled函数和onrejected函数返回一个Promise实例的情况并不困难。但是需要将变量reuslt满足既可以是一个普通值,也可以是一个Promise实例。我们可以抽象出resolvePromise方法进行统一处理。

const resolvePromise = (promise2, result, resolve, reject) => {}

现在的任务就是完成resolvePromise函数,这个函数接受的参数如下:

  • promise2: 返回的Promise实例。
  • result: onfulfilled或onrejected函数返回的实例。
  • resolve: promise2的resolve方法。
  • reject: promise2的reject方法。
const resolvePromise = (promise2, result, resolve, reject) => {
  // 当result和promise2相等时,可能出现死循环,为了避免,在这里会执行reject
  if(result === promise2) {
    reject(new TypeError('error due to circular refrence'))
  }
// 是否已经执行过onfulfilled或onrejected
  let consumed = false
  let thenable

  // 如果返回的是Promise,则调用resolvePromise去包装返回的promise2
  if (result instanceof Promise) {
    if(result.status === 'pending') {
      result.then(function(data) {
        resolvePromise(promise2, data, resolve, reject)
      }, reject)
    } else {
      result.then(resolve, reject)
    }
    return
  }

  let isComplexResult = target => (typeof target === 'function' || typeof target === 'object') && (target !== null)
  // 如果返回的是疑似Promise类型
  if(isComplexResult(result)) {
    try{
      thenable = result.then
      // 判断返回的是疑似Promise类型
      if(typeof thenable === 'function') {
        thenable.call(result, function(data) {
          if(consumed) {
            return
          }
          consumed = true
          return resolvePromise(promise2, data, resolve, reject)
        }, function(error) {
          if(consumed) {
            return
          }
          consumed = true
          return reject(error)
        })
      }
     else { 
        resolve(result)
      }
    } catch(e) {
      if(consumed) {
        return
      }
      consumed = true
      return reject(e)
    }
  }
  else {
    resolve(result)
  }
}

如果reuslt不是Promise实例,不是对象,也不是函数,而是一个普通值的话,则可以直接对promise2进行决议。
对于onfulfilled函数返回的结果result:如果result含有then属性方法,那么我们称该属性方法为thenable,说明result是一个Promise实例,当执行该实例的then方法时,返回结果可能是一个Promise实例类型,也可能是一个普通值,因此还要递归resolvePromise。
以下是完整代码:

function Promise(executor) {
  this.status =  'pending'
  this.value = null
  this.reason = null
  
  this.onFulfilledArray = []
  this.onRejectedArray = []
  
  const resolve = (value) => {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'
  
        this.onFulfilledArray.forEach(func => {
          func(value)
        })
       }
    })
  }
  const reject = (value) =>  {
    setTimeout(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'rejected'

        this.onRejectedArray.forEach(func => {
          func(value)
        })
       }
    })
  }
  executor(resolve, reject)
}
Promise.prototype.then = function (onfulfilled, onrejected) {
  let promise2
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
  if(this.status === 'fulfilled') {
    return promise2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        try{
          // 将onfulfilled处理后的结果赋给新的promise2的resolve
          let result = onfulfilled(this.value)
          resolvePromise(promise2, result, resolve, reject)
        } catch(e) {
          reject(e)
        }
      })
    })
  }
  if(this.status === 'rejected') {
    return promise2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        try{
          // 将onrejected处理后的结果赋给新的promise2的resolve
          let result = onrejected(this.value)
          resolvePromise(promise2, result, resolve, reject)
        } catch(e) {
          reject(e)
        }
      })
    })
  }
  if(this.status === 'pending') {
    return promise2 = new Promise((resolve, reject) => {
      this.onFulfilledArray.push(() => {
        try{
          let result = onfulfilled(this.value)
          resolvePromise(promise2, result, resolve, reject)
        } catch(e) {
          reject(e)
        }
      })
      this.onRejectedArray.push(() => {
        try{
          let result = onrejected(this.reason)
          resolve(result)
        } catch(e) {
          reject(e)
        }
      })
    })
  }
}

7.Promise穿透实现

此时的Promise基本已经实现了95%,不是100%是因为有一处细节没有完成。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('lucas')
  },2000)
})
promise.then(null)
.then(data => {
  console.log(data)
})

这段代码会在2s后输出lucas。这就是Promise穿透现象:给then()函数传递非函数值为其参数时,实际上会被解析成then(null),这时,上一个Promise对象的决议结果便会被“穿透”到下一个then方法中。
如何实现Promise穿透呢?
其实上面的代码已经实现了。

  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}

如果onfulfilled不是函数,则给一个默认值,该默认值是返回其参数的函数。

你可能感兴趣的:(从“手写Promise”中可以学到什么?)