回调函数、异步、手写Promise

69 阅读6分钟

先说一下什么是回调函数

回调函数就是一个函数a作为实参传入另一个函数b,并在b中执行a这个函数,这就是回调函数,代码形式如下

function b(callback){
  console.log('我是b的打印')
  callback()
}
b(function(){
  console.log('我是a的打印')
})
//执行结果是:我是b的打印   我是a的打印

如上代码,function(){console.log('我是a的打印')} ,这个函数就是一个回调函数

当然了,回调函数也可以传参,比如

function a(callback)
{
  let data
  setTimeout(() => {
    data = "我是数据"
  }, 2000)
  callback(data)
}

a((a) => {
  console.log(a) //我是数据
})

通过这种方法,很明显。我们可以通过回调函数进行异步操作。

但是如果多层嵌套,就会造成回调地狱问题

  • 代码难看
  • 难以理解和维护
  • 捕获异常要用try catch,比较臃肿

Promise

它代表了一个尚未完成但预期在将来完成的操作。使用Promise,可以避免所谓的“回调地狱”。

executor中执行异步操作,利用then函数回传

new Promise( function(resolve, reject) { /* executor */
    // 执行代码 需要指明resolve与reject的回调位置
});

//executor是带有resolve和reject两个参数的函数。Promise构造函数执行时立即调用executor函数,resolve和reject两个函数作为参数传递给executor。

promise有三种状态

  • pending: 初始状态,既不是成功,也不是失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

Promise对象只有从pending变为fulfilled和从pending变为rejected的状态改变。只要处于 fulfilled rejected ,状态就不会再变了。

优点:

  • 通过链式调用,避免了深度嵌套的回调函数,使得代码更加清晰易读
  • Promise实例之间可以轻松组合和复用,使得代码更加模块化和灵活。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果

缺点:

  • Promise对象一旦创建后,其状态就不可再改变。这意味着无法取消或者终止Promise实例的执行。
  • 虽然Promise内置了错误处理机制,但有时错误处理可能不够直观,特别是在处理多个并发Promise时,可能会出现不易定位和调试的问题。
  • 当处于 pending 状态时,无法得知目前进展到哪一个阶段

手写Promise

参考: 面试官:请手写一个Promise

首先实现一下简单的Promise

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

export class Promise {
  constructor(executor) {
    this.value = undefined
    this.reason = undefined
    this.status = PENDING

    const resolve = (value) => {
      if (this.status === PENDING) {
        this.value = value
        this.status = FULFILLED
      }
    }
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.reason = reason
        this.status = REJECTED
      }
    }
    executor(resolve, reject)
  }
  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled && onFulfilled(this.value) //如果noFulfilled不为空,那就那就执行,为空的话由于是&&,就不会调用了
    }
    if (this.status === REJECTED) {
      onRejected && onRejected(this.reason)
    }
  }
}

这个初始完成的Promise比较简单,可以自己起个node试用一下

上面我们写的Promise有一个问题,那就是只能处理同步操作

比如我们这样

import { Promise } from './Promise.js'

const promise = new Promise((resolve, reject) => {
  //传入回调函数,接收Promise的resolve函数和reject函数
  setTimeout(() => {
    const i = 3
    resolve(i)
  }, 2000)
})

promise.then((value) => {
  console.log(value)
})

//这样就不会返回任何值,因为在调用then函数的时候,Promise还没有改变状态,而且我们目前的then函数中对于没有改变状态,也就是pending状态的Promise没有做出响应。

改进(实现异步):

在then方法调用时,如果Promise还处在pending状态,那我们就先法then中传入的回调函数存起来,等到Promise改变状态之后(也就是调用resolve或reject后)再一并执行。这样也就实现了异步。

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

export class Promise {
  constructor(executor) {
    this.value = undefined
    this.reason = undefined
    this.status = PENDING
    this.onFulfilledCallbacks = [] //存储异步操作成功之后的回调函数
    this.onRejectedCallbacks = [] // 存储异步操作失败之后的回调函数

    const resolve = (value) => {
      if (this.status === PENDING) {
        this.value = value
        this.status = FULFILLED
        this.onFulfilledCallbacks.forEach((callback) => callback(this.value)) //统一执行
      }
    }
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.reason = reason
        this.status = REJECTED
        this.onResolvedCallbacks.forEach((callback) => callback(this.value)) //统一执行
      }
    }
    executor(resolve, reject)
  }
  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled && onFulfilled(this.value) //如果noFulfilled不为空,那就那就执行,为空的话由于是&&,就不会调用了
    } else if (this.status === REJECTED) {
      onRejected && onRejected(this.reason)
    } else {   //如果时pending状态就先存起来
      if (onFulfilled) this.onFulfilledCallbacks.push(onFulfilled)
      if (onRejected) this.onRejectedCallbacks.push(onFulfilled)
    }
  }
}

改进(实现链式调用)

非常巧妙,读了半天才懂

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

export class Promise {
  constructor(executor) {
    this.value = undefined
    this.reason = undefined
    this.status = PENDING
    this.onResolvedCallbacks = [] //存储异步操作成功之后的回调函数
    this.onRejectedCallbacks = [] // 存储异步操作失败之后的回调函数

    const resolve = (value) => {
      if (this.status === PENDING) {
        this.value = value
        this.status = FULFILLED
        this.onResolvedCallbacks.forEach((callback) => callback())
      }
    }
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.reason = reason
        this.status = REJECTED
        this.onRejectedCallbacks.forEach((callback) => callback())
      }
    }
    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  // 改良链式调用的then有两种作用:1. 如果onFulfilled或者onRejected返回的是一个真正的Promise的时候,将这个Promise返回2. 如果不是的话(假设return x),要返回一个Fulfilled状态的Promise对象,并将这个值传到返回的Promise中的value中去
  then(onFulfilled, onRejected) {
    // 关键的函数          新Promise  return值  新Promise的resolve和reject
    const resolvePromise = (promise2, x, resolve, reject) => {
      //这段代码的目的是防止 Promise 链中出现循环引用导致的死循环。在 Promise 的链式调用中,如果某个 Promise 的 then 方法返回了它本身,就会形成一个循环引用,导致 Promise 链无法正常结束,进而造成死循环。
      if (promise2 === x) {
        return reject(new TypeError('UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>'))
      }

      let called = false
      // 判断x的类型 x是对象或函数才有可能是一个promise
      if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        try {
          const then = x.then
          if (typeof then === 'function') {
            // 只能认为它是一个promise
            then.call(
              x,
              (y) => {
                if (called) return
                called = true
                resolvePromise(promise2, y, resolve, reject) //递归处理,直到x不是promise或者余姚reject
              },
              (r) => {
                if (called) return
                called = true
                reject(r)
              }
            )
          } else {
            resolve(x)
          }
        } catch (e) {
          if (called) return
          called = true
          reject(e)
        }
      } else {
        //如果返回的数不是Promise 那就直接调用resolve函数将Promise2变为Fulfilled的然后返回
        console.log('我这里变成了Fulfilled')
        resolve(x)
      }
    }
    const promise2 = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // onFulfilled方法可能返回值或者promise
        console.log('我是fulfilled')
        const x = onFulfilled(this.value)
        resolvePromise(promise2, x, resolve, reject)
      }
      if (this.status === REJECTED) {
        // onRejected方法可能返回值或者promise
        console.log('我是rejected')
        const x = onRejected(this.reason)
        resolvePromise(promise2, x, resolve, reject)
      }
      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
          const x = onFulfilled(this.value)
          console.log('x', x, typeof x)
          resolvePromise(promise2, x, resolve, reject)
        })
        this.onRejectedCallbacks.push(() => {
          const x = onRejected(this.reason)
          resolvePromise(promise2, x, resolve, reject)
        })
      }
    })
    return promise2
  }
}

然后再在一些关键的地方增加js提供的调度微任务的函数就可以啦

const promise2 = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // onFulfilled方法可能返回值或者promise
        queueMicroTask(() => {
          try {
            console.log('我是fulfilled')
            const x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      if (this.status === REJECTED) {
        // onRejected方法可能返回值或者promise
        queueMicroTask(() => {
          try {
            console.log('我是rejected')
            const x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      if (this.status === PENDING) {
        queueMicroTask(() => {
          try {
            this.onResolvedCallbacks.push(() => {
              const x = onFulfilled(this.value)
              console.log('x', x, typeof x)
              resolvePromise(promise2, x, resolve, reject)
            })
          } catch (e) {
            reject(e)
          }
        })
      }
    })