Promise实现原理1【如何实现一个最基础的Promise】

192 阅读4分钟

本文的目的主要是为了带大家认识Promise实现原理,只实现主要流程,会分为三个大知识点给大家分享Promise

目录

  • Promise实现原理1【如何实现一个最基础的Promise】
  • Promise实现原理2【如何实现链式调用】
  • Promise实现原理3【如何实现Promise执行reject后如何停掉Promise的链式调用】

Promise基本用法

new Promise((resolve, reject) => {
  resolve('成功')
  reject('失败')
}).then(res => {
  console.log(res)
}, err => {
  console.log(err)
})

初步分析可得

  • 首先 Promise 是一个构造函数
  • 这个构造函数接收一个回调函数作为参数初始化时即刻执行(官方这样说到“The executor function is called with the ability to resolve or reject the promise”),立即执行后返回两个函数 resolve、reject成功和失败。
  • 当执行reslove时会执行then的第一个参数,当执行reject时会执行then的第二个参数

初步编写可得

class Promise {
	constructor(executor) {
  	const resolve = (res) => {
      this.resFn(res)
    }

    const reject = (error) => {
      this.errorFn(error)
    }

    // 初始化时立即执行
    executor(resolve, reject)
  }

  then(resFn, errorFn) {
    // 把函数放置到resolve 和 reject可访问的地方
  	this.resFn = resFn
    this.errorFn = errorFn
  }
}

这个时候我们会发现调用reject、resolve会报错 this.resFn is not a function 或 this.errorFn is not a function是因为我们在执行 resolve 时还没有执行 then方法,回调函数还没有挂载,我们需要等待主线程执行完成后再执行then不能同步执行。重点:通过 queueMicrotask 使用微任务。

微任务

class Promise {
	constructor(executor) {
  	const resolve = (res) => {
      queueMicrotask(() => {
        this.resFn(res)
      })
    }

    const reject = (error) => {
      queueMicrotask(() => {
        this.errorFn(res)
      })
    }

    // 初始化时立即执行
    executor(resolve, reject)
  }

  then(resFn, errorFn) {
    // 把函数放置到resolve 和 reject可访问的地方
  	this.resFn = resFn
    this.errorFn = errorFn
  }
}

这时候我们的then就实现了一个异步调用。

Promise 状态

官网这样描述Promise状态

  • 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled):意味着操作成功完成。
  • 已拒绝(rejected):意味着操作失败。
  • Promise初始状态为pending,一旦状态发生改变不可再进行修改。

由此可得

class Promise {
	constructor(executor) {
    // 把三种状态设置为常量
    const STATE_PENDING = 'pending',
      STATE_FULFILLED = 'fulfilled',
      STATE_REJECTED = 'rejected'

    // 初始状态
    this.state = STATE_PENDING
  	const resolve = (res) => {
      // 只有状态为等待中时才允许修改状态
      if (this.state === STATE_PENDING) {
        queueMicrotask(() => {
        	this.state = STATE_FULFILLED
          this.resFn(res)
        })
      }
    }

    const reject = (error) => {
      // 只有状态为等待中时才允许修改状态
      if (this.state === STATE_PENDING) {
        queueMicrotask(() => {
        	this.state = STATE_REJECTED
          this.errorFn(res)
        })
      }
    }

    // 初始化时立即执行
    executor(resolve, reject)
  }

  then(resFn, errorFn) {
    // 把函数放置到resolve 和 reject可访问的地方
  	this.resFn = resFn
    this.errorFn = errorFn
  }
}

这样一个简易的Promise函数就实现了,多次调用then会被重置。那如何实现多次调用呢?接下来我们来优化then方法实现多次调用。

优化then方法实现多次调用

基础实现

class Promise {
	constructor(executor) {
    // 把三种状态设置为常量
    const STATE_PENDING = 'pending',
      STATE_FULFILLED = 'fulfilled',
      STATE_REJECTED = 'rejected'

    // then队列
    this.errorFnList = []
    this.resFnList = []

    // 初始状态
    this.state = STATE_PENDING
  	const resolve = (res) => {
      // 只有状态为等待中时才允许修改状态
      if (this.state === STATE_PENDING) {
        queueMicrotask(() => {
        	this.state = STATE_FULFILLED
          // this.resFn(res)
          this.resFnList.forEach(fn => fn(res))
        })
      }
    }

    const reject = (error) => {
      // 只有状态为等待中时才允许修改状态
      if (this.state === STATE_PENDING) {
        queueMicrotask(() => {
        	this.state = STATE_REJECTED
          // this.errorFn(res)
          this.errorFnList.forEach(fn => fn(res))
        })
      }
    }

    // 初始化时立即执行
    executor(resolve, reject)
  }

  then(resFn, errorFn) {
    // 把函数放置到resolve 和 reject可访问的地方
  	// this.resFn = resFn
   	//  this.errorFn = errorFn
    this.resFnList.push(resFn)
    this.errorFnList.push(errorFn)
  }
}

const p1 = new Promise((resolve, reject) => {
  resolve(2323)
})

// 这样我们多次调用的时候就不会冲掉上次的结果啦
p1.then(res => console.log(res))
p1.then(res => console.log(res))

// 但如果我们在异步队列里面调用
setTimeout(() => {
  // 这个时候then不调用了
  p1.then(res => console.log(res))
})

是不是这样就万事大吉利了呢?按照电视剧剧情发展当然不是,必定是有坑点的,那么接下来我们就看看坑点在哪

解决坑点

这个时候我们添加一个宏任务,再执行then调用时发现 then失效了不会被调用,其实是因为我们的状态 queueMicrotask 时发生了变化 “一旦状态发生改变不可再进行修改。”

class Promise {
  // 把三种状态设置为常量
  static STATE_PENDING = 'pending';
  static STATE_FULFILLED = 'fulfilled';
  static STATE_REJECTED = 'rejected';

  constructor(executor) {
    // then队列
    this.errorFnList = []
    this.resFnList = []

    // 返回值
    this.value = undefined
    this.error = undefined

    // 初始状态
    this.state = Promise.STATE_PENDING
    const resolve = (res) => {
      // 只有状态为等待中时才允许修改状态
      if (this.state === Promise.STATE_PENDING) {
        queueMicrotask(() => {
          this.state = Promise.STATE_FULFILLED
          // this.resFn(res)
          this.value = res
          this.resFnList.forEach(fn => fn(res))
        })
      }
    }

    const reject = (error) => {
      // 只有状态为等待中时才允许修改状态
      if (this.state === Promise.STATE_PENDING) {
        this.error = error
        queueMicrotask(() => {
          this.state = Promise.STATE_REJECTED
          // this.errorFn(error)
          this.error = error
          this.errorFnList.forEach(fn => fn(error))
        })
      }
    }

    // 初始化时立即执行
    executor(resolve, reject)
  }

  then(resFn, errorFn) {
    // 把函数放置到resolve 和 reject可访问的地方
    // this.resFn = resFn
    //  this.errorFn = errorFn

    // 如果在调用then时状态已经确定,则直接调用
    if (this.state === Promise.STATE_FULFILLED) {
      resFn && resFn(this.value)
    } else if (this.state === Promise.STATE_REJECTED) {
      errorFn && errorFn(this.error)
    } else {
      this.resFnList.push(resFn)
      this.errorFnList.push(errorFn)
    }
  }
}

p1.then(res => console.log(res))
setTimeout(() => {
  // 这样就能正常执行了
  p1.then(res => console.log(res))
})

核心知识点

  • 回调机制

  • 使用 queueMicrotask 微任务

  • then实现多次调用

下一篇:Promise实现原理2【如何实现链式调用】