Promise一些思考

117 阅读11分钟

Promise一些思考

我们之前一直都是在强调如何手写promise,但是和工作上的使用场景没有联系上,这篇文章主要是从工作角度和实用性上探讨promise

一、接口请求

我们在工作中,经常会碰到这种情况。一个文件导出请求的api封装,然后另外一个文件在某个时机(初始化)的时候执行函数,发起请求,然后再then函数中进行一些处理。

let getData = function () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        name: 'hcc'
      })
    }, 1000)
  })
}
getData().then(res => {
  console.log(res)
})

思考: 为什么可以这样呢?Promise是基于什么逻辑可以做到这样呢?这里有三个点需要思考

  1. new Promise()返回了什么
  2. resolve(xxx)是什么和做了什么
  3. then()做了什么

二、 new Promise做了什么

new Promise(fn)接受一个函数参数,在没有resolve或者reject的时候,都是pending状态,返回了本质上是一个对象,有一些方法(then, catch, finally)。

class Promise {
  constructor(fn) {
    this.state = PENDING
    this.result = null
    this.callbacks = []
  }
  then(onFulfilled, onRejected) {}
  ****
}

三、resolve()是什么和做了什么

我们知道Promise的有一个函数参数,接受2个参数,那这2个函数到底是什么呢?

class Promise { 
   constructor(fn) {
    let done = false

    const onFulfilled = (value) => transition(this, FULFILLED, value)

    const resolve = (value) => {
      if (done) return
      done = true
      resolvePromise(this, value, onFulfilled, onRejected)
    }
    fn(resolve, reject)
  }
}

const resolvePromise = (promise, x, resolve, reject) => {
  resolve(x)
}

const transition = (promise, state, result) => {
  if (promise.state !== PENDING) return
  promise.state = state
  promise.result = result
  // 这里处理then注册的callback,因此需要通过其他platform Api实现异步调用
  runAsync(() => {
    while (promise.callbacks.length) {
      handleCallback(promise.callbacks.shift(), state, result) // 处理callback
    }
  })
}

从上面的简单代码中,我们可以看出我们在调用resolve(data)的时候,本质上只做了2件事情

  • 将我们的promise实例的state改成FULFILLED和result改成传递进来的参数data
  • 在下一个宏任务中执行promise实例的callbacks

问题: 那么问题来了,我们什么时候给实例的callbacks添加回到函数呢?

四、 then() 做了什么

通过上面的调用resolve()我们明白,在调用resolve()后,promise实例的状态已经从pending变成fulfilled,并将传递的值赋值到result

主要的作用有以下几点

  1. 每次then()函数都会返回一个新的promise实例
  2. 将then函数接受的2个函数参数进行处理,如果是pending状态就放入**callbacks**中,如果不是的话,就下一个任务执行
  then(onFulfilled, onRejected) {
    return new Promise((resolve, reject) => {
      const callback = { resolve, reject, onFulfilled, onRejected }

      if (this.state === PENDING) {
        this.callbacks.push(callback)
      } else {
        runAsync(() => handleCallback(callback, this.state, this.result))
      }
    })
  }

五、正常场景流程分析

5.1 我们直接执行promise和单个then

let getData = function () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        name: 'hcc'
      })
    }, 1000)
  })
}
getData().then(res => {
  console.log(res)
})
  1. 当执行getData()的时候,首先会执行Promise的构造函数constructor, 触发setTimeout(发起请求),将函数放入下一个宏任务中等待执行,此时promise实例还是pending状态

  2. 返回Promise的实例,执行then方法,传入onFulfilled的回调函数,注意此时还没有执行resolve(xxx),通过上面then函数做了什么,我们可以知道,此时then()会将onFulfilled的处理放入实例的callbacks中

    return new Promise((resolve, reject) => {
        const callback = { resolve, reject, onFulfilled, onRejected }
    
        if (this.state === PENDING) {
            this.callbacks.push(callback)
        }
    }
    
  3. then()执行完成后,开始执行下一个宏任务,会调用resolve({name: 'hcc' })

    // 1. 实际resolve执行的是这个函数
    const onFulfilled = (value) => transition(this, FULFILLED, value)
    const resolve = (value) => {
        if (done) return
        done = true
        resolvePromise(this, value, onFulfilled, onRejected)
    }
    
    // 2. 接下来会执行resolvePromise,进行一些对value值的判断,这里我们传递的是要给对象没有额外的处理,省略掉
    const resolvePromise = (promise, x, resolve, reject) => {
      resolve(x)
    }
    
    // 3. 实际就是执行了(value) => transition(this, FULFILLED, value)
    const transition = (promise, state, result) => {
      if (promise.state !== PENDING) return
    
      promise.state = state
      promise.result = result
    
      // 这里处理then注册的callback,因此需要通过其他platform Api实现异步调用
      runAsync(() => {
        while (promise.callbacks.length) {
          handleCallback(promise.callbacks.shift(), state, result)
        }
      })
    }
    

    transition()会将当前promise实例从pending 转换成 fulfilled ,此时promise.callbacks中已经有我们上一个then()传入的callbacks值,所以会执行到handleCallback

  4. handleCallback()的执行, 其中callback对象的四个参数是如下的来源

    const handleCallback = (callback, state, result) => {
      const { resolve, reject, onFulfilled, onRejected } = callback
      try {
        if (state === FULFILLED) {
          isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result)
        }
      } catch (error) {
        reject(error)
      }
    }
    
    • resolve/reject : 第二步中then() 新返回的promise的resolve/reject

    • onFulfilled/onRejected: 是第二步中then() 的接受的2个入参,

    • 由于我们传递的是一个函数onFulfilled, 所以会执行onFulfilled 并传入实例result的值,并将返回值传递给then()返回新的promise的resolve中

      // then接受的第一个参数  ->  onFulfilled
      res => {
        console.log(res)
      }
      

这里一个整个流程就完成了。这里做一个总结:

未命名文件 (14).png

5.2 直接执行promise并链式调用then

let getData = function () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(1)
    }, 1000)
  })
}
getData().then(res => {
  console.log(res)
  return res + 1
}).then(res => {
  console.log('res -- 2', res)
})

我们看上面的代码,链式调用then()方法,那这个流程又是怎么样的呢?

5.1的第四点中,我们知道了then()会返回一个新的promise实例, 那么链式调用,就会调用上一个then()方法返回的实例,并传入一个回调函数(onFulfilled)。

此时全部都是同步执行,所有的Promise都处于pending状态,所以回调任务都被放入没有给promsie的callbacks中。等待同步任务执行完成,主线程空闲,去执行第一个resolve(1), 从5.1的第四点中,我们知道了resolve(1),本质上会去执行transition, 改变每一个promise实例的状态和值, 异步调用callbacks

整体流程总结:

  1. 执行getData(), 执行构造函数,执行传入的参数Fn, 并执行setTimeout, 放入宏任务队列,等待执行
  2. 返回一个Promise实例(A),执行A的then()方法,传入一个函数作为参数(onFulfilled
  3. then()函数中,再次执行新的new Promise(), 构造函数里面进行封装callback参数(包含4个参数), 但是由于此时A还处于pending状态, 所以会将封装的callback放入实例A的callbacks中,返回新的promise实例B
  4. 执行实例B的then方法,由于实例B也处于pending状态,所以此时B实例的then()方法执行的逻辑也是将参数onFulfilled放入实例B自身的callbacks属性中,再次返回一个新的promise实例C, 结束主线程执行。
  5. 然后接下来开始下一个宏任务,执行实例A的resolve()
    • 将实例A的状态从pending 改成 fulfilled
    • 改变实例A的result值(为resolve的入参)
    • 新增异步队列,处理实例A中的callbacks值,结束resolve的调用,主线程结束
    • 下一个队列开始执行(刚刚新增的处理A的callbacks值的函数),执行handleCallback, resolve执行完毕
    const { resolve, reject, onFulfilled, onRejected } = callback
    if (state === FULFILLED) {
      isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result)
    } 
    
  6. 此时实例A的resolve执行完毕,由于onFulfilled是一个函数(之前A实例调用的then方法传入的第一个函数),会执行callback中的resolve(执行then方法的时候,新增的实例B的resolve), 将函数的值传递给实例B的resolve, 进行下一轮的调用。
  7. 实例B的resolve开始调用, 进行复制和新的状态改变,然后异步执行实例B的callbacks,至此,流程结束。

5.3 promise值传递

最近我在研究小程序的预请求的时候,有一个场景是,将一个promise用一个对象进行存放,当需要的时候进行获取,然后获取接口的返回值。例如这样

// 文件A
let getData = function () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(1)
    }, 1000)
  })
}
// 文件B 在之后的某一个时间需要去处理
let a = getData()
setTimeout(() => {
  a.then(res => {
    console.log(res)
  })
}, 3000)

这也是符合promsie承诺的意思,我调用后,在之后的某一个时间需要,然后取值的时候可以取到。那这个过程的内部其实是什么样的呢?

整体流程:

  1. 执行getData(), 然后执行内部的new Promise(Fn)
  2. 执行Fn(), 调用setTimeout,将resolve放入宏任务队列中等待下一次执行,返回promise的实例,赋值给a, 主流程执行完毕。
  3. 遇到下一个setTimeout(), 将回调函数放入宏队列,等待执行,主线程结束。
  4. 主线程执行完毕,开始检查宏任务队列,1s后,执行resolve(1), 将a的状态改成fulfilled并进行赋值操作,由于此时a中的callbacks的值为空,所以不会有执行什么操作,主线程结束。
  5. 主线程执行完毕,开始检查宏任务队列,3s后,执行a的then函数, 传入onFulfilled,返回一个新的promise, 然后promise的内部执行传入的函数fn, 由于此时a.then()中的this(a)的state的状态已经不再是pending了, 所以会将() => handleCallback(callback, this.state, this.result)放入宏任务队中,等待执行,主线程结束。
    then(onFulfilled, onRejected) {
      return new Promise((resolve, reject) => {
        const callback = { resolve, reject, onFulfilled, onRejected }
    
        if (this.state === PENDING) {
          this.callbacks.push(callback)
        } else {
          runAsync(() => handleCallback(callback, this.state, this.result))
        }
      })
    }
    
  6. 主线程执行完毕,开始检查宏任务队列,执行handleCallback, 进行值的处理,将result的值放入到onFulfilled执行,这样我们就可以拿到之前的resolve的值了。
    isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result)
    

5.4 传入的值为Promise的情况

我们在工作中也会遇到下面这种场景,resolve() 的值接收到了一个promise, 这种情况下,我们应该怎么理解我们的代码的执行呢?

let getData = function () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(Promise.resolve(1))
    }, 1000)
  })
}
let a = getData()
a.then(res => {
  console.log(res)
})

主要思路解析:

  1. 这里前期还是和上面的一样,执行getData
  2. 然后执行Promise的构造函数,执行then方法将onFulfilled放入实例a的callbacks
  3. 下一个宏任务执行到a实例的resolve()事件,本质上会执行resolvePromise 发现是一个promise类型
    const resolvePromise = (promise, x, resolve, reject) => {
      if (isPromise(x)) {
        return x.then(resolve, reject)
      }
      resolve(x)
    }
    
  4. 这里的x就是我们传递进来的Promise.resolve(1)的一个promise对象(b),这里会调用bthen()方法,而resolve传递的是实例a的
  5. 执行实例b的then方法, 由于实例b已经是fulfilled状态,所以执行它的then()方法会直接将处理callback的函数放入宏队列中。此时的callback中的4个参数分别代表如下:
    • resolve/reject: 代表then函数新建的promsie实例
    • onFulfilled/onRejected: 代表a实例的promise构造函数中封装的onFulfilled/onRejected
    then(onFulfilled, onRejected) {
      return new Promise((resolve, reject) => {
        const callback = { resolve, reject, onFulfilled, onRejected }
        runAsync(() => handleCallback(callback, this.state, this.result))
      })
    }
    
  6. 接下来执行b实例的handleCallback,由于b实例的状态已经是fulfilled, 所以会执行这一行代码
    const handleCallback = (callback, state, result) => {
      const { resolve, reject, onFulfilled, onRejected } = callback
      isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result)
    }
    
  7. 将b实例的result(1)传递给onFulfilled,而onFulfilled函数正好是实例a的resolve的封装,这样就链接上了,然后正常走实例a的resolve流程,修改状态和值,执行callbacks

六,总结

上面讲解了promise的内部实现,本质上resolve()就是promise包装的一个函数,用于改变promsie实例的值,then()会根据promsie的状态,对回调函数进行处理。 最后附上完整代码,用于和上面的知识点进行对比~

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

const isObject = (obj) => obj !== null && typeof obj === 'object'
const isFunction = (fn) => typeof fn === 'function'
const isPromise = (p) => p instanceof Promise
const isThenable = (obj) => (isObject(obj) || isFunction(obj)) && 'then' in obj && isFunction(obj.then)
let i = 1
class Promise {
  constructor(fn) {
    this.state = PENDING
    this.result = null
    this.callbacks = []
    this.name = i++
    let done = false

    const onFulfilled = (value) => transition(this, FULFILLED, value)
    const onRejected = (reason) => transition(this, REJECTED, reason)

    const resolve = (value) => {
      console.log('constructor-resolve-------' , this.name)
      if (done) return
      done = true
      resolvePromise(this, value, onFulfilled, onRejected)
    }

    const reject = (reason) => {
      if (done) return
      done = true
      onRejected(reason)
    }

    try {
      fn(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  // 2.2.7 then方法必须返回一个Promise
  then(onFulfilled, onRejected) {
    console.log('---constructor--then--------', this.name)
    return new Promise((resolve, reject) => {
      const callback = { resolve, reject, onFulfilled, onRejected }

      if (this.state === PENDING) {
        this.callbacks.push(callback)
      } else {
        runAsync(() => handleCallback(callback, this.state, this.result, this.name))
      }
    })
  }

  catch(onRejected) {
    return this.then(null, onRejected)
  }

  finally(onFinally) {
    return this.then(
        (res) => Promise.resolve(onFinally()).then(() => res),
        (err) =>
            Promise.resolve(onFinally()).then(() => {
              throw err
            })
    )
  }

  // implement es6
  static resolve(value) {
    if (isPromise(value)) return value
    if (isThenable(value)) return new Promise((resolve, reject) => value.then(resolve, reject))
    return new Promise((resolve) => resolve(value))
  }

  static reject(reason) {
    return new Promise((_, reject) => reject(reason))
  }
}

// promise 状态迁移的公共函数
const transition = (promise, state, result) => {
  console.log('-transition', promise.name)
  if (promise.state !== PENDING) return

  promise.state = state
  promise.result = result

  // 这里处理then注册的callback,因此需要通过其他platform Api实现异步调用
  runAsync(() => {
    console.log('-runAsync-transition', state, promise.name)
    while (promise.callbacks.length) {
      handleCallback(promise.callbacks.shift(), state, result, promise.name)
    }
  })
}

const handleCallback = (callback, state, result, name) => {
  const { resolve, reject, onFulfilled, onRejected } = callback
  // 2.2
  try {
    if (state === FULFILLED) {
      console.log('-handleCallback', state, name)
      isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result)
    } else if (state === REJECTED) {
      isFunction(onRejected) ? resolve(onRejected(result)) : reject(result)
      // 如果有注册catch或者onRejected
    }
  } catch (error) {
    reject(error)
  }
}

// 2.3 Promise Resolution Procedure
const resolvePromise = (promise, x, resolve, reject) => {
  console.log('resolve-------' , promise.name)
  // 2.3.1
  if (x === promise) {
    return reject(new TypeError(''))
  }

  // 2.3.2
  if (isPromise(x)) {
    console.log('resolve-------' , promise.name)
    return x.then(resolve, reject)
  }

  // 2.3.3 if x is an object or function
  // Note that the specification does not require x to be thenable here.
  if (isFunction(x) || isObject(x)) {
    // 2.3.3.2 retrieving the x.then
    try {
      const then = x.then
      // 3.5
      if (isFunction(then)) {
        return new Promise(then.bind(x)).then(resolve, reject)
      }
    } catch (error) {
      return reject(error)
    }
  }

  resolve(x)
}
let getData = function () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(Promise.resolve(1))
    }, 1000)
  })
}
let a = getData()
a.then(res => {
  console.log(res)
})
/*setTimeout(() => {
  a.then(res => {
    console.log(res)
  })
}, 3000)*/
const runAsync = (cb) => {
  setTimeout(() => {
    cb()
  }, 0)
}

/*
const runAsyncWithMutationObserver = (cb) => {
  const observer = new MutationObserver(cb)
  const textNode = document.createTextNode('1')
  observer.observe(textNode, { characterData: true })
  textNode.data = '2'
}

const runAsyncWithMessageChannel = (cb) => {
  const channel = new MessageChannel()
  channel.port1.onmessage = cb
  channel.port2.postMessage(1)
}
*/