likes
comments
collection
share

阅读 Promise A+ 规范,动手实现Promise

作者站长头像
站长
· 阅读数 47

背景

Promise/A+规范

动手实现Promise,先由 github issues 中相关的问题引入吧。

export function testPromiseCatch() {
	const p2 = new Promise((resolve, reject) => {
		setTimeout(() => {
			const e = {message: 'rejected', name: 'Error'}
			// const e = new Error('rejected')
			reject(e)
		}, 2000)
	})

	p2.then(n => {
		throw new Error('then() should not run.')
	})

	// .catch(e => {
	p2.catch(e => {
		if (e.message != 'rejected') throw new Error('It should have rejected with the correct error')
		console.log('ERROR WAS CAUGHT!')
	})

	console.log('Created promise, wait for result...')
}

testPromiseCatch()
--------
@MacBook-Pro nodedemo % node test.js 
[inside indirect, before creating rejected promise]
[inside indirect, before wait]
file:///Users/nodedemo/test.js:11
  const x = Promise.reject(new Error("An Error"))
                           ^

Error: An Error
    at indirect (file:///Users/nodedemo/test.js:11:28)
    at file:///Users/nodedemo/test.js:20:1
    at ModuleJob.run (node:internal/modules/esm/module_job:192:25)
    at async DefaultModuleLoader.import (node:internal/modules/esm/loader:228:24)
    at async loadESM (node:internal/process/esm_loader:40:7)
    at async handleMainPromise (node:internal/modules/run_main:66:12)

Node.js v20.4.0

为什么会崩呢

阅读 Promise A+ 规范,动手实现Promise

站在使用者的角度,去实现Promise,有几个关键点

状态

我们在使用的时候知道promise有三个状态:Pending, Fulfilled , Rejected

  • 处于 Pending 时,promise 可以迁移至 Fullfilled 或 Rejected
  • 处于 Fulfilled 时,promise 必须拥有一个不可变的终值且不能迁移至其他状态
  • 处于 Rejected 时,promise 必须拥有一个不可变的拒绝原因且不能迁移至其他状态
  • 还需要改变状态的方法,我们定义 resolve 和 reject函数,作为构造函数的参数

那么初始化状态就这样

class MyPromise {
  constructor(executor) {
    this.status = 'pending' // pending rejected fulfilled
    this.value = undefined
    this.reason = undefined
    this.resolveCallbacks = []
    this.rejectCallbacks = []

    let resolve = (value) => {
      if (this.status === "pending") {
        this.status = "fulfilled"
        this.value = value
        this.resolveCallbacks.forEach((fn) => fn())
      }
    }

    let reject = (reason) => {
      if (this.status === "pending") {
        this.status = "rejected"
        this.reason = reason
        this.rejectCallbacks.forEach((fn) => fn())
      }
    }

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

Then 方法

我们在使用的时候,通常是这样的。

const promise = new Promise((resolve, reject) => {
  resolve('Success!')
})

promise.then((value) => {
  console.log(value)
}, (err) => {
  console.log(err)
})  // Expected output: "Success!"

那么就是then 方法

  1. 需要接受两个可选参数,需要时函数onFulfilled, onRejected

  2. onFulfilled 的参数就是 promise 的终值

  3. onRejected 的参数就是 promise 被拒绝的原因

  4. 还需要返回一个 promise 这样才能继续 then

接着还会这样用,在onFulfilled中进行一系列操作,return一个value或者再return一个Promise。

举以下几个例子,其中promise1 promise2 promise3,这些就是then方法不同的返回值。

const promise = new Promise((resolve, reject) => {
  resolve('Success!')
})

const promise1 = promise.then((value) => {
  return Promise.resolve(value)
})
promise1.then(v => {
  console.log(v)
}) // "Success!"

const promise2 = promise.then((value) => promise2)
promise2.then(v => {
  console.log(v)
}, e => {
  console.log(e)
}) // [TypeError: Chaining cycle detected for promise #<Promise>]

const prmise3 = promise.then((value) => {
  throw new Error("error error")
})
prmise3.then(v => {
  console.log(v)
}).catch(e => {
  console.log(e)
}) // > Error: error error
  1. 如果 onFulfilled 或者 onRejected 抛出一个异常 e情况就是上面的promise3例子,那么返回的 promise3 必须拒绝执行,并返回拒绝的原因 e
  2. 如果 onFulfilled 或者 onRejected 返回了一个值 x,会执行 promise 的解决过程
    • 如果 x 和返回的 promise 相等,情况就是上面的promise2例子,也就是 promise2 和 x 指向同一对象时,以 TypeError 作为拒绝的原因拒绝执行 promise2
    • 如果 x 是 promise,情况就是上面的promise1例子,会判断 x 的状态。如果是等待态,保持;如果是完成态,用相同的值执行 promise;如果是拒绝态,用相同的拒绝原因拒绝 promise
    • 如果 x 是对象或者函数,将 x.then 赋值给 then;如果取 x.then 的值时抛出错误 e ,则以 e 为拒绝原因拒绝 promise。如果 then 是函数,将 x 作为函数的 this,并传递两个回调函数 resolvePromise, rejectPromise 作为参数调用函数

所以我们先定义一个promise的解决,具体实现可以先不写。

function resolvePromise (promise, x, resolve, reject),会有递归调用,反正最终要解决这个promise,解决的手段就是resolve或者reject,所以也要作为参数传递。

综上 then 方法如下

  then (onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : function resolve (value) {
      return value
    }
    onRejected = typeof onRejected === "function" ? onRejected : function reject (err) {
      throw err
    }
    let promise = new MyPromise((resolve, reject) => {
      if (this.status === "fulfilled") {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value)
            resolvePromise(promise, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }

      if (this.status === "rejected") {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }

      if (this.status === "pending") {
        this.resolveCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value)
              resolvePromise(promise, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })

        this.rejectCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason)
              resolvePromise(promise, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
      }
    })

    return promise
  }

再根据 resolvePromise 要实现的内容

function resolvePromise (promise, x, resolve, reject) {
  if (promise === x) {
    return reject(
      new TypeError("Chaining cycle detected for promise #<Promise>")
    )
  }
  let called
  if ((typeof x === "object" && x != null) || typeof x === "function") {
    try {
      let then = x.then
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            if (called) return
            called = true
            resolvePromise(promise, y, resolve, reject)
          },
          (r) => {
            if (called) return
            called = true
            reject(r)
          }
        )
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}

完整代码如下

class MyPromise {
  constructor(executor) {
    this.status = 'pending' // pending rejected fulfilled
    this.value = undefined
    this.reason = undefined
    this.resolveCallbacks = []
    this.rejectCallbacks = []

    let resolve = (value) => {
      if (this.status === "pending") {
        this.status = "fulfilled"
        this.value = value
        this.resolveCallbacks.forEach((fn) => fn())
      }
    }

    let reject = (reason) => {
      if (this.status === "pending") {
        this.status = "rejected"
        this.reason = reason
        this.rejectCallbacks.forEach((fn) => fn())
      }
    }

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

  then (onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : function resolve (value) {
      return value
    }
    onRejected = typeof onRejected === "function" ? onRejected : function reject (err) {
      throw err
    }
    let promise = new MyPromise((resolve, reject) => {
      if (this.status === "fulfilled") {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value)
            resolvePromise(promise, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }

      if (this.status === "rejected") {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }

      if (this.status === "pending") {
        this.resolveCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value)
              resolvePromise(promise, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })

        this.rejectCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason)
              resolvePromise(promise, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
      }
    })

    return promise
  }

}

function resolvePromise (promise, x, resolve, reject) {
  if (promise === x) {
    return reject(
      new TypeError("Chaining cycle detected for promise #<Promise>")
    )
  }
  let called
  if ((typeof x === "object" && x != null) || typeof x === "function") {
    try {
      let then = x.then
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            if (called) return
            called = true
            resolvePromise(promise, y, resolve, reject)
          },
          (r) => {
            if (called) return
            called = true
            reject(r)
          }
        )
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}

const promise = new Promise((resolve, reject) => {
  resolve('Success!')
})

const promise1 = promise.then((value) => {
  return Promise.resolve(value)
})
promise1.then(v => {
  console.log(v)
}) // "Success!"

const promise2 = promise1.then((value) => promise2)
promise2.then(v => {
  console.log(v)
}, e => {
  console.log(e)
}) // [TypeError: Chaining cycle detected for promise #<Promise>]

结语

各位看官,下期见

探索实践 Next.js 处理流数据~

Node事件循环和浏览器事件循环有什么区别?

转载自:https://juejin.cn/post/7363195315053363252
评论
请登录