异步编程

513 阅读4分钟

一. 概述

1. 异步模式

  • 异步模式的 api 不会等待这个任务的结束才开始下一个任务
  • 对于耗时操作(异步操作)开启过后就立即往后执行下一个任务
  • 后续逻辑一般会通过回调函数的方式定义

2. 回调函数

  • 所有异步编程方案的根基
  • 由调用者定义,交给执行者执行的函数

3. Promise

  • CommonJs 社区提出了 Promise 规范

  • 在 ECMA 2015 中被标准化,成为语言规范

  • Promise 实际是一个对象,用来表示一个异步任务在执行过后是成功还是失败,状态一旦确定,不会改变

  • 状态

    • pending 等待
    • fulfilled 成功 -> onFulfilled
    • rejected 失败 -> onRejected
  • 基本用法

    const promise = new Promise((resolve, reject) => {
      // resolve(100) // 成功
      reject(new Error('promise rejected')) // 失败
    })
    
    promise.then(res => {
      console.log(res)
    }, err => {
      console.log(err)
    })
    
  • Promise 使用案例

    function ajax(url) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.open('GET', url)
        xhr.responseType = 'json'
        xhr.onload = function () {
          if (this.status === 200) {
            resolve(this.response)
          } else {
            reject(new Error(this.statusText))
          }
        }
        xhr.send()
      })
    }
    
  • Promise 链视调用

    • Promise 对象的 then 方法会返回一个权限的 Promise 对象
    • 后面的 then 方法就是在为上一个 then 方法返回的 Promise 注册回调
    • 前面的 then 方法中回调函数的返回值会作为后面 then 方法回调的参数
    • 如果回调返回的是 Promise,后面的 then 方法的回调会等待它的结束
    const { resolve } = require("path")
    
    const promise = new Promise((resolve, reject) => {
      resolve(1)
    })
    
    promise.then(res => {
      console.log(res)
      return new Promise(resolve => {
        resolve(2)
      })
    }).then(res => {
      console.log(res)
    })
    
  • Promise 静态方法

    • Promise.resolve() 返回一个状态为 fulfilled 的 Promise 对象
    • Promise.reject() 返回一个状态为 rejected 的 Promise 对象
    • Promise.all() 并行执行,等待所有任务结束才结束,需要一个数组作为参数,数组中存放通过 promise 实现的异步任务,返回一个 Promise 对象
    • Promise.race() 并行执行,等待其中一个任务结束就结束,需要一个数组作为参数,数组中存放通过 promise 实现的异步任务,返回一个 Promise 对象
  • 宏任务和微任务

    • 宏任务:回调队列中的任务
    • 微任务:宏任务执行过程中可以的一些额外需求,Promise 的回调作为微任务执行
      • 常见微任务:
        • Promise 回调
        • MutationObserver
        • process.nextTick()

二. Promise 源码

1. 须知

  • Promise 是一个类,在执行这个类的时候需要传递一个执行器,执行器会立即执行
  • Promise 中有三种状态
    • 成功 fulfilled
    • 失败 rejected
    • 等待 pending
      • pending 可以转变为 fulfilled
      • pending 可以转变为 rejected
      • 一旦状态改变就不能更改
  • resolve 和 reject 函数是用来更改状态
    • resolve 是把状态变为成功
    • reject 是把状态变为失败
  • then 方法内部就是判断状态,如果状态为成功,调用成功回调函数,如果状态失败,调用失败回调函数,then 方法是被定义在原型对象中
  • then 成功回调有一个参数,表示成功之后的值,then 失败回调有一个参数,表示失败之后的错误
  • 同一个 promise 对象下面的 then 方法是可以被调用多次的
  • then 方法是可以被链式调用的, 后面 then 方法的回调函数拿到值的是上一个 then 方法的回调函数的返回值

2. 模拟 Promise

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

class MyPromise {
  constructor(executor) {
    try {
      executor(this.resolve, this.reject)
    } catch (err) {
      this.reject(err)
    }
  }

  status = PENDING
  value = undefined
  error = undefined
  successCallback = []
  failCallback = []

  resolve = value => {
    if (this.status !== PENDING) {
      return
    }
    this.status = FULFILLED
    this.value = value
    while (this.successCallback.length) {
      this.successCallback.shift()()
    }
  }

  reject = error => {
    if (this.status !== PENDING) {
      return
    }
    this.status = REJECTED
    this.error = error
    while (this.failCallback.length) {
      this.failCallback.shift()()
    }
  }

  then(successCallback, failCallback) {
    successCallback = successCallback ? successCallback : value => value
    failCallback = failCallback ? failCallback : error => { throw error }
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            const x = successCallback(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            const x = failCallback(this.error)
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else {
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              const x = successCallback(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
        this.failCallback.push(() => {
          setTimeout(() => {
            try {
              const x = failCallback(this.error)
              resolvePromise(promise2, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
      }
    })
    return promise2
  }

  finally(callback) {
    return this.then(value => {
      return MyPromise.resolve(callback()).then(() => value)
    }, error => {
      return MyPromise.reject(callback()).then(undefined, () => { throw error })
    })
  }

  catch(failCallback) {
    return this.then(undefined, failCallback)
  }

  static all(array) {
    let result = []
    let index = 0
    return new MyPromise((resolve, reject) => {
      function addData(key, value) {
        result[key] = value
        index++
        if (index === array.length) {
          resolve(result)
        }
      }
      for (let i = 0; i < array.length; i++) {
        const current = array[i]
        if (current instanceof MyPromise) {
          current.then(value => addData(i, value), error => reject(error))
        } else {
          addData(i, current)
        }
      }
    })
  }

  static resolve(value) {
    if (value instanceof MyPromise) {
      return value
    }
    return new MyPromise(resolve => resolve(value))
  }

  static reject(error) {
    if (error instanceof MyPromise) {
      return error
    }
    return new MyPromise(undefined, reject => reject(error))
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  if (x instanceof MyPromise) {
    x.then(resolve, reject)
  } else {
    resolve(X)
  }
}

module.exports = MyPromise