js中如何顺序执行异步任务

本文介绍了JavaScript中处理异步任务的常见方法,从回调函数到Promise,再到generator和async/await。详细讲解了Promise的实现原理和A+测试,以及generator和async/await的基本使用和异步任务的顺序执行。
摘要由CSDN通过智能技术生成

在js中,任务可分为两种,同步任务和异步任务。

(1) 同步任务

又叫 非耗时任务,指的是在主线程排队执行的那些任务
只有前一个任务执行完毕,才能执行后一个任务

(2) 异步任务

又叫 耗时任务,异步任务由JavaScript委托给宿主环境进行执行
当异步任务执行完成后,会通知JavaScript主线程执行异步任务的回调函数
当我们打开网站时,像图片的加载,音乐的加载,其实就是一个异步任务

现有a、b和c三个任务,如果其为同步任务,可以很简单地顺序执行,但如果其为异步任务,该如何顺序执行呢?

一、回调函数

function thing(thingName, callback) {
    setTimeout(() => {
        console.log(`执行${thingName}任务`)
        typeof callback === 'function' && callback()
    }, 1000)
}

// 执行a任务
// 执行b任务
// 执行c任务
thing('a', () => {
    thing('b', () => {
        thing('c')
    })
})

优点:简单、方便、实用

缺点:回调函数层层嵌套,不易于阅读和维护,形成回调地狱。

二、promise

1. 使用方式

基本使用

new Promise ((resolve, reject) => {
  // 执行代码
}).then(() => {
  // 期约兑现
}).catch(() => {
  // 期约拒绝
})

详细使用戳这里

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise#%E7%A4%BA%E4%BE%8B

2. 异步任务顺序执行

function thing(thingName) {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`执行${thingName}任务`)
            resolve()
        }, 1000)
    })
}

// 执行a任务
// 执行b任务
// 执行c任务
thing('a')
    .then(() => thing('b'))
    .then(() => thing('c'))

3. 实现原理

那么如何实现一个promise呢?实现promise之前,先分析下promise的结构

  1. promise存在三个状态,pending 待定, fulfilled 兑现, rejected 拒绝,因此需要

    (1)定义三个常量 PENDINGFULFILLEDREJECTED 对应三种状态

    (2)定义 status 表示期约当前状态

    (3)定义 value 表示已兑现期约值、定义 reason 表示已拒绝期约原因

  2. 在调用promise的实例函数时,我们传入了一个执行器,执行器接收两个函数,其作用分别为将待定期约转化为已兑现期约与已拒绝期约。因此需要定义两个函数 resolve reject

  3. 已兑现期约、已拒绝期约处理函数 then

    已拒绝期约处理函数 catch

    最终执行函数 finally

  4. 静态函数 resolverejectall race 的实现

代码实现基于以下链接调整

https://juejin.cn/post/6945319439772434469#heading-30

3.1 promise简易实现
// MyPromise.js

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

class MyPromise {
  // 期约状态, 初始值为pending
  status = PENDING
  // 已兑现期约值
  value = null
  // 已拒绝期约原因
  reason = null

  constructor(executor){
    executor(this.resolve, this.reject)
  }

  // 将待定期约转化为已兑现期约
  resolve = (value) => {
    if (this.status === PENDING) {
      this.status = FULFILLED
      this.value = value
    }
  }

  // 将待定期约转化为已拒绝期约
  reject = (reason) => {
    if (this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
    }
  }

  then (onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value)
    } else if (this.status === REJECTED) {
      onRejected(this.reason)
    }
  }
}

module.exports = MyPromise

同目录下新建 test.js 文件用于测试

// test.js

// 引入我们的 MyPromise.js
const MyPromise = require('./MyPromise')
const promise1 = new MyPromise((resolve, reject) => {
   resolve('resolve')
})

const promise2 = new MyPromise((resolve, reject) => {
    reject('reject')
})

promise1.then(value => {
  console.log('promise1 then', value)
}, reason => {
  console.log('promise1 catch', reason)
})

promise2.then(value => {
    console.log('promise2 then', value)
  }, reason => {
    console.log('promise2 catch', reason)
  })

// 执行结果
// promise1 then resolve
// promise2 catch reject
3.2 加入异步逻辑

继续测试,发现异步执行resolve函数时,会存在问题。因此,我们需对异步逻辑进行处理。

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
   setTimeout(() => resolve('resolve'), 0)
})

promise.then(value => {
    console.log('promise then', value)
  }, reason => {
    console.log('promise catch', reason)
  })

// 期望输出 promise then resolve
// 实际无输出

(1) 缓存兑现与拒绝回调

// 存储兑现回调函数
onFulfilledCallback = null
// 存储拒绝回调函数
onRejectedCallback = null

(2) then 方法中新增待定期约处理

then (onFulfilled, onRejected) {
  if (this.status === FULFILLED) {
    onFulfilled(this.value)
  } else if (this.status === REJECTED) {
    onRejected(this.reason)
  }  else if (this.status === PENDING) {
    this.onFulfilledCallback = onFulfilled
    this.onRejectedCallback = onRejected
  }
}

(3) resolve 与 reject 中调用回调函数

  // 将待定期约转化为已兑现期约
  resolve = (value) => {
    if (this.status === PENDING) {
      this.status = FULFILLED
      this.value = value
      // 兑现回调函数存在则执行
      this.onFulfilledCallback && this.onFulfilledCallback(value)
    }
  }

  // 将待定期约转化为已拒绝期约
  reject = (reason) => {
    if (this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
      // 拒绝回调函数存在则执行
      this.onRejectedCallback && this.onRejectedCallback(reason)
    }
  }

使用以下代码再次验证,异步问题解决。

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
   setTimeout(() => resolve('resolve'), 0)
})

promise.then(value => {
    console.log('promise then', value)
  }, reason => {
    console.log('promise catch', reason)
  })

// 执行结果: promise then resolve
3.3 实现 then 方法多次调用添加多个处理函数

Promise支持添加多个处理函数,来测试下自定义Promise是否满足。

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 2000) 
})

promise.then(value => {
  console.log(1)
  console.log('resolve', value)
})
 
promise.then(value => {
  console.log(2)
  console.log('resolve', value)
})

promise.then(value => {
  console.log(3)
  console.log('resolve', value)
})

// 3
// resolve success

经测试,自定义Promise并不能添加多个处理函数,继续修改。

(1) 新增兑现与拒绝回调数组

// 存储兑现回调函数数组
onFulfilledCallbacks = []
// 存储拒绝回调函数数组
onRejectedCallbacks = []

(2) then方法中存储回调

then (onFulfilled, onRejected) {
  if (this.status === FULFILLED) {
    onFulfilled(this.value)
  } else if (this.status === REJECTED) {
    onRejected(this.reason)
  }  else if (this.status === PENDING) {
    this.onFulfilledCallbacks.push(onFulfilled)
    this.onRejectedCallbacks.push(onRejected)
  }
}

(3) resolve 与 reject 中循环调用回调函数

// 将待定期约转化为已兑现期约
resolve = (value) => {
  if (this.status === PENDING) {
    this.status = FULFILLED
    this.value = value
    // 兑现回调函数存在则执行
    while (this.onFulfilledCallbacks.length) {
      // Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
      this.onFulfilledCallbacks.shift()(value)
    }
  }
}

// 将待定期约转化为已拒绝期约
reject = (reason) => {
  if (this.status === PENDING) {
    this.status = REJECTED
    this.reason = reason
    // 拒绝回调函数存在则执行
    while (this.onRejectedCallbacks.length) {
      // Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
      this.onRejectedCallbacks.shift()(reason)
    }
  }
}

再次测试,问题解决。

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 2000) 
})

promise.then(value => {
  console.log(1)
  console.log('resolve', value)
})
 
promise.then(value => {
  console.log(2)
  console.log('resolve', value)
})

promise.then(value => {
  console.log(3)
  console.log('resolve', value)
})

// 1
// resolve success
// 2
// resolve success
// 3
// resolve success
3.4 实现then方法的链式调用

promise是支持链式调用的,我们用自定义的promise来测试下,看是否满足。

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
  // 目前这里只处理同步的问题
  resolve('success')
})

function other () {
  return new MyPromise((resolve, reject) =>{
    resolve('other')
  })
}
promise.then(value => {
  console.log(1)
  console.log('resolve', value)
  return other()
}).then(value => {
  console.log(2)
  console.log('resolve', value)
  
// TypeError: Cannot read property 'then' of undefined

可以看到,第一个then函数的返回值为undefined,不能链式调用。继续修改

class MyPromise {
	......

  then (onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        const x = onFulfilled(this.value)
        // 传入 resolvePromise 集中处理
        resolvePromise(x, resolve, reject)
      } else if (this.status === REJECTED) {
        onRejected(this.reason)
      }  else if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(onFulfilled)
        this.onRejectedCallbacks.push(onRejected)
      }
    })

    return promise2
  }
}

function resolvePromise(x, resolve, reject) {
  // 判断x是不是 MyPromise 实例对象
  if(x instanceof MyPromise) {
    // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
    // x.then(value => resolve(value), reason => reject(reason))
    // 简化之后
    x.then(resolve, reject)
  } else{
    // 普通值
    resolve(x)
  }
}

测试,完成链式调用。

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
  // 目前这里只处理同步的问题
  resolve('success')
})

function other () {
  return new MyPromise((resolve, reject) =>{
    resolve('other')
  })
}
promise.then(value => {
  console.log(1)
  console.log('resolve', value)
  return other()
}).then(value => {
  console.log(2)
  console.log('resolve', value)
})

// 1
// resolve success
// 2
// resolve other
3.5 then 方法链式调用识别 Promise 是否返回自己

如果 then 方法返回的是自己的 Promise 对象,则会发生循环调用,这个时候程序会报错,原生Promise测试如下

// test.js

const promise = new Promise((resolve, reject) => {
    resolve(100)
  })

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

// 100
// UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>

在 MyPromise 实现一下

// MyPromise.js

class MyPromise {
  ......
  then (onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        const x = onFulfilled(this.value)
        // 传入 resolvePromise 集中处理
        resolvePromise(promise2, x, resolve, reject)
      } else if (this.status === REJECTED) {
        onRejected(this.reason)
      }  else if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(onFulfilled)
        this.onRejectedCallbacks.push(onRejected)
      }
    })

    return promise2
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  // 如果相等了,说明return的是自己,抛出类型错误并返回
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }

  // 判断x是不是 MyPromise 实例对象
  if(x instanceof MyPromise) {
    // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
    // x.then(value => resolve(value), reason => reject(reason))
    // 简化之后
    x.then(resolve, reject)
  } else{
    // 普通值
    resolve(x)
  }
}
// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
    resolve('success')
})
 
const p1 = promise.then(value => {
   console.log('resolve', value)
   return p1
})
 

运行一下,结果报错了。从错误提示可以看出,我们必须要等 p1 完成初始化。这里就需要创建一个异步函数去等待 p1 完成初始化,此处使用微任务 --> queueMicrotask

image.png

修改并执行

// MyPromise.js

class MyPromise {
  ......

	then (onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        queueMicrotask(() => {
          const x = onFulfilled(this.value)
          // 传入 resolvePromise 集中处理
          resolvePromise(promise2, x, resolve, reject)
        })
      } else if (this.status === REJECTED) {
        onRejected(this.reason)
      }  else if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(onFulfilled)
        this.onRejectedCallbacks.push(onRejected)
      }
    })

    return promise2
  }
}
// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
    resolve('success')
})
 
const p1 = promise.then(value => {
   console.log('resolve1', value)
   return p1
})
 
// 运行的时候会走reject
p1.then(value => {
  console.log('resolve2', value)
}, reason => {
  console.log('reject')
  console.log(reason.message)
})

// 执行结果
// resolve1 success
// reject
// Chaining cycle detected for promise #<Promise>
3.6 错误处理

(1) 捕获执行器错误

// MyPromise.js

class MyPromise {
  ......

	constructor(executor){
    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      // 如果有错误,就直接执行 reject
      this.reject(error)
    }
  }
}

测试一下

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
    // resolve('success')
    throw new Error('执行器错误')
})

// 2
// 执行器错误
promise.then(value => {
  console.log(1)
  console.log('resolve', value)
}, reason => {
  console.log(2)
  console.log(reason.message)
})

测试通过。

(2) then 执行的时错误捕获

// MyPromise.js

class MyPromise {
  ......

	then (onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        try {
          queueMicrotask(() => {
            const x = onFulfilled(this.value)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          })
        }  catch (error) {
          reject(error)
        }  
      } else if (this.status === REJECTED) {
        onRejected(this.reason)
      }  else if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(onFulfilled)
        this.onRejectedCallbacks.push(onRejected)
      }
    })

    return promise2
  }
}

测试一下

// test.js

const MyPromise = require('./MyPromise')

const promise = new MyPromise((resolve, reject) => {
    resolve('success')
 })
 
//  1
//  resolve success
//  4
//  then error
promise.then(value => {
  console.log(1)
  console.log('resolve', value)
  throw new Error('then error')
}, reason => {
  console.log(2)
  console.log(reason.message)
}).then(value => {
  console.log(3)
  console.log(value)
}, reason => {
  console.log(4)
  console.log(reason.message)
})

测试通过。

3.7 rejected及pending状态改造
  1. 增加异步状态下的链式调用
  2. 增加回调函数执行结果的判断
  3. 增加识别 Promise 是否返回自己
  4. 增加错误捕获
// MyPromise.js

class MyPromise {
  ......

then (onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      const fulfilledMicrotask = () =>  {
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          try {
            // 获取成功回调函数的执行结果
            const x = onFulfilled(this.value)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          } 
        })  
      }
  
      const rejectedMicrotask = () => { 
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          try {
            // 调用失败回调,并且把原因返回
            const x = onRejected(this.reason)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          } 
        }) 
      }

      if (this.status === FULFILLED) {
        fulfilledMicrotask()
      } else if (this.status === REJECTED) {
        rejectedMicrotask()
      }  else if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(fulfilledMicrotask)
        this.onRejectedCallbacks.push(rejectedMicrotask)
      }
    })

    return promise2
  }
}
3.8 then 中的参数变为可选

上面我们处理 then 方法的时候都是默认传入 onFulfilled、onRejected 两个回调函数,但是实际上原生 Promise 是可以选择参数的单传或者不传,都不会影响执行。

例如下面这种 👇

// test.js

const promise = new Promise((resolve, reject) => {
  resolve(100)
})

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

// 输出 100

所以我们需要对 then 方法做一点小小的调整

// MyPromise.js

class MyPromise {
  ......

	then (onFulfilled, onRejected) {
    // 如果不传,就使用默认函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

    const promise2 = new MyPromise((resolve, reject) => {
      const fulfilledMicrotask = () =>  {
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          try {
            // 获取成功回调函数的执行结果
            const x = onFulfilled(this.value)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          } 
        })  
      }
  
      const rejectedMicrotask = () => { 
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          try {
            // 调用失败回调,并且把原因返回
            const x = onRejected(this.reason)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          } 
        }) 
      }

      if (this.status === FULFILLED) {
        fulfilledMicrotask()
      } else if (this.status === REJECTED) {
        rejectedMicrotask()
      }  else if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(fulfilledMicrotask)
        this.onRejectedCallbacks.push(rejectedMicrotask)
      }
    })

    return promise2
  }
}

改造完自然是需要验证一下的

先看情况一:resolve 之后

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
  resolve('succ')
})
 
promise.then().then().then(value => console.log(value))

// 打印 succ

再看情况二:reject 之后

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
  reject('err')
})
 
promise.then().then().then(value => console.log(value), reason => console.log(reason))

// 打印 err
3.9 catch、finally函数实现

(1) catch 其实是个语法糖,可以通过then函数传入第二个参数实现

(2) finally 的特点如下

无论promise成功或失败,finally方法都会执行接收到的回调函数,并返回一个promise实例:
a. 如果回调函数执行出错,将以抛出的错误,拒绝新的promise;
b. 否则,新返回的promise会沿用旧promise的决议值进行决议。

// MyPromise.js

class MyPromise {
  ......

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

  // 无论promise成功或失败,finally方法都会执行接收到的回调函数,并返回一个promise实例:
  // 1. 如果回调函数执行出错,将以抛出的错误,拒绝新的promise;
  // 2. 否则,新返回的promise会沿用旧promise的决议值进行决议。
  finally (callback) {
    return this.then(
      (data) => {
        callback()
        return data
      },
      (error) => {
        callback()
        throw error
      }
    )
  }
}
3.10 静态函数实现

(1) resolve

  static resolve (parameter) {
    // 如果传入 MyPromise 就直接返回
    if (parameter instanceof MyPromise) {
      return parameter
    }

    return new MyPromise(resolve =>  {
      resolve(parameter)
    })
  }

(2) reject

// MyPromise.js

class MyPromise {
  ......

  static reject (reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason)
    })
  }
}

(3) all

// MyPromise.js

class MyPromise {
  ......

  static all (promiseArray) {
    let result = []
    let success = 0

    return new Promise((resolve, reject) => {
      promiseArray.forEach((promise, i) => {
        MyPromise.resolve(promise)
          .then(res => {
            result[i] = res
            success++

            // 全部成功
            if (success === promiseArray.length) {
              resolve(result)
            }
          }, err => reject(err))
      })
    }) 
  }
}

(4) race

// MyPromise.js

class MyPromise {
  ......

  static race (promiseArray) {
    return new Promise((resolve, reject) => {
      promiseArray.forEach((promise) => {
        MyPromise.resolve(promise)
          .then(res => {
              resolve(res)
          }, err => reject(err))
      })
    }) 
  }
}

验证一下,木有问题

// test.js

const MyPromise = require('./MyPromise')

// resolve success
MyPromise
  .resolve('success')
  .then((res) => {
    console.log('resolve', res)
  })

// reject fail
MyPromise
  .reject('fail')
  .then(() => {}, err => {
    console.log('reject', err)
  })

// all [ 'a', 'b', 'c' ]
MyPromise
  .all([thing('a', 1000), thing('b', 2000), thing('c', 3000)])
  .then(res => console.log('all', res))

// race a
MyPromise
  .race([thing('a', 1000), thing('b', 2000), thing('c', 3000)])
  .then(res => console.log('race', res))


function thing (thingName, timeout) {
  return new MyPromise(
    resolve => setTimeout(() => {
      resolve(thingName)
    }, timeout)
  )
}
3.11 最终代码
// MyPromise.js

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

class MyPromise {
  // 期约状态, 初始值为pending
  status = PENDING
  // 已兑现期约值
  value = null
  // 已拒绝期约原因
  reason = null

  // 存储兑现回调函数数组
  onFulfilledCallbacks = []
  // 存储拒绝回调函数数组
  onRejectedCallbacks = []

  constructor(executor){
    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      // 如果有错误,就直接执行 reject
      this.reject(error)
    }
  }

  // resolve 静态方法
  static resolve (parameter) {
    // 如果传入 MyPromise 就直接返回
    if (parameter instanceof MyPromise) {
      return parameter
    }

    return new MyPromise(resolve =>  {
      resolve(parameter)
    })
  }

  // reject 静态方法
  static reject (reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason)
    })
  }

  static all (promiseArray) {
    let result = []
    let success = 0

    return new Promise((resolve, reject) => {
      promiseArray.forEach((promise, i) => {
        MyPromise.resolve(promise)
          .then(res => {
            result[i] = res
            success++

            // 全部成功
            if (success === promiseArray.length) {
              resolve(result)
            }
          }, err => reject(err))
      })
    }) 
  }

  static race (promiseArray) {
    return new Promise((resolve, reject) => {
      promiseArray.forEach((promise) => {
        MyPromise.resolve(promise)
          .then(res => {
              resolve(res)
          }, err => reject(err))
      })
    }) 
  }

  // 将待定期约转化为已兑现期约
  resolve = (value) => {
    if (this.status === PENDING) {
      this.status = FULFILLED
      this.value = value
      // 兑现回调函数存在则执行
      while (this.onFulfilledCallbacks.length) {
        // Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
        this.onFulfilledCallbacks.shift()(value)
      }
    }
  }

  // 将待定期约转化为已拒绝期约
  reject = (reason) => {
    if (this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
      // 拒绝回调函数存在则执行
      while (this.onRejectedCallbacks.length) {
        // Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
        this.onRejectedCallbacks.shift()(reason)
      }
    }
  }

  then (onFulfilled, onRejected) {
    // 如果不传,就使用默认函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

    const promise2 = new MyPromise((resolve, reject) => {
      const fulfilledMicrotask = () =>  {
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          try {
            // 获取成功回调函数的执行结果
            const x = onFulfilled(this.value)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          } 
        })  
      }
  
      const rejectedMicrotask = () => { 
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          try {
            // 调用失败回调,并且把原因返回
            const x = onRejected(this.reason)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          } 
        }) 
      }

      if (this.status === FULFILLED) {
        fulfilledMicrotask()
      } else if (this.status === REJECTED) {
        rejectedMicrotask()
      }  else if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(fulfilledMicrotask)
        this.onRejectedCallbacks.push(rejectedMicrotask)
      }
    })

    return promise2
  }

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

  finally (finalFunc) {
    return this.then(finalFunc, finalFunc)
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  // 如果相等了,说明return的是自己,抛出类型错误并返回
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }

  // 判断x是不是 MyPromise 实例对象
  if(x instanceof MyPromise) {
    // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
    // x.then(value => resolve(value), reason => reject(reason))
    // 简化之后
    x.then(resolve, reject)
  } else{
    // 普通值
    resolve(x)
  }
}

module.exports = MyPromise

4. Promise A+ 测试

4.1 测试

检验一份手写 Promise 靠不靠谱,通过 Promise A+ 规范自然是基本要求,这里我们可以借助 promises-aplus-tests 来检测我们的代码是否符合规范。

(1) 新建test文件夹

(2) 命令行输入以下代码快速初始化

npm init -y

(3) 安装包

npm install promises-aplus-tests -D

(4) 将 MyPromise.js 拷贝至test文件夹,并添加 deferred 函数

// MyPromise.js

......

MyPromise.deferred = function () {
  const result = {}
  result.promise = new MyPromise(function (resolve, reject) {
    result.resolve = resolve
    result.reject = reject
  })

  return result
}

module.exports = MyPromise

(5) 配置启动命令

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "promises-aplus-tests MyPromise"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "promises-aplus-tests": "^2.1.2"
  }
}

(6) 命令行输入以下代码执行

node run test
4.2 修改

执行结果如图

image.png

image.png

可以看出是2.3.x的问题,翻看了一下 Promise A+ 规范,找到以下信息。

Promise A+ 规范链接:https://promisesaplus.com/#point-59image.png

翻译如下

2.3.3 否则如果 x 是一个对象或函数,

​ 2.3.3.1 将x.then赋值给then

​ 2.3.3.2 如果取 x.then 的值时抛出错误 e,则以 e为由拒绝 promise。

​ 2.3.3.3 如果then是一个函数,执行并将其this指向x,第一个入参为resolvePromise,第二个入参为rejectPromise

​ 2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)

​ 2.3.3.3.2 如果 rejectPromise以r 为参数被调用,则以r为由拒绝promise

​ 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用

​ 2.3.3.3.4 如果then执行时抛出异常

​ 2.3.3.3.4.1 如果resolvePromise 或 rejectPromise 被调用,则忽略它

​ 2.3.3.3.4.2 否则,以e为由拒绝promise

​ 2.3.3.4 如果then不是一个函数,则以x为值兑现promise

2.3.4 如果x不是一个对象或函数,则以x为值兑现promise

代码修改如下

function resolvePromise(promise, x, resolve, reject) {
  // 如果相等了,说明return的是自己,抛出类型错误并返回
  if (promise === x) {
    return reject(new TypeError('The promise and the return value are the same'))
  }

  if (typeof x === 'object' || typeof x === 'function') {
    // x 为 null 直接返回,走后面的逻辑会报错
    if (x === null) {
      return resolve(x)
    }

    let then
    try {
      // 把 x.then 赋值给 then 
      then = x.then
    } catch (error) {
      // 如果取 x.then 的值时抛出错误 error ,则以 error 为据因拒绝 promise
      return reject(error)
    }

    // 如果 then 是函数
    if (typeof then === 'function') {
      let called = false
      try {
        then.call(
          x, // this 指向 x
          // 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
          y => {
            // 如果 resolvePromise 和 rejectPromise 均被调用,
            // 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
            // 实现这条需要前面加一个变量 called
            if (called) return
            called = true
            resolvePromise(promise, y, resolve, reject)
          },
          // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
          r => {
            if (called) return
            called = true
            reject(r)
          })
      } catch (error) {
        // 如果调用 then 方法抛出了异常 error:
        // 如果 resolvePromise 或 rejectPromise 已经被调用,直接返回
        if (called) return

        // 否则以 error 为据因拒绝 promise
        reject(error)
      }
    } else {
      // 如果 then 不是函数,以 x 为参数执行 promise
      resolve(x)
    }
  } else {
    // 如果 x 不为对象或者函数,以 x 为参数执行 promise
    resolve(x)
  }
}

再次执行,完美解决。

image.png

三、generator

generator函数,即生成器函数,是一个状态机,封装了多个内部状态。执行一个generator,会返回一个迭代器对象,通过迭代器对象,可以遍历generator函数内部的每个状态。因此,generator函数可以看做是一个迭代器生成器。

1. 基本使用

(1) generator基本形式

generator 函数是在 function 和函数名之间添加 * 来定义的。yield关键字可以让生成器停止和开始执行,生成器函数在遇到yield关键字之前会正常执行。遇到这个关键字之后,执行会停止,函数作用域的状态会被保留。停止执行的生成器函数可通过next()方法来恢复执行。

function *foo() {
    yield 1
    yield 2
    yield 3
    return 4
}

(2) 执行foo得到一个迭代器

const iterator = foo()

(3) 迭代器遍历

可以通过常用迭代器遍历方法如for-of来遍历迭代器

// 1
// 2
// 3
for(let item of iterator){
    console.log(item)
}

也可以手动进行遍历

// 1
// 2
// 3
let item = iterator.next()
while (!item.done) {
    console.log(item.value)
    item = iterator.next()
}

2. 异步任务顺序执行

function thing(thingName) {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`执行${thingName}任务`)
            resolve()
        }, 1000)
    })
}

function *generator() {
    yield thing('a')
    yield thing('b')
    yield thing('c')
}

function dothings (generator) {
    const thingIterator = generator()
    let thingItem
    exec()

    function exec () {
        thingItem = thingIterator.next()
        if (!thingItem.done) {
            Promise.resolve(thingItem.value).then(exec)
        }
    }
}

// 执行a任务
// 执行b任务
// 执行c任务
dothings(generator)

四、async/await

1. 实现原理

1)async/await 就是 generator 的语法糖,使得异步操作变得更加方便
2)async 函数就是将 generator 函数的星号(*)替换成 async,将 yield 替换成await

async function thing(thingName) {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`执行${thingName}任务`)
            resolve()
        }, 1000)
    })
}

// generator 生成器函数
function myAsync (generator) {
    return function () {
        const iterator = generator()
        let item

        return new Promise ((resolve, reject) => exec(resolve, reject))

        function exec (resolve, reject) {
            item = iterator.next()

            if (!item.done) {
               Promise.resolve(item.value).then(() => exec(resolve, reject))
            } else {
                // 返回值处理
                if (item.value instanceof Promise) {
                    item.value.then(resolve, reject)
                } else {
                    resolve(item.value)
                }
            }
        }
    }
}

function *generator() {
    yield thing('a')
    yield thing('b')
    yield thing('c')

    return 'the end'
}

// 模拟定义一个async函数
const myAsyncFunc = myAsync(generator)
// 执行a任务
// 执行b任务
// 执行c任务
const result = myAsyncFunc()

// 返回为一个promise对象
console.log(result instanceof Promise) // true
// the end
result.then(res => console.log(res))

2. 使用注意事项

1)await 后面的promise对象,当其为一个已拒绝期约时,会阻塞后面代码的执行,举例如下

// the first
// UnhandledPromiseRejectionWarning: undefined
async function test () { 
    console.log('the first')
    await Promise.reject()
    console.log('the second')
}

test()

对此,可以使用catch函数或try-catch进行处理

catch函数

// the first
// the second
async function test () { 
    console.log('the first')
    await Promise.reject().catch(() => {})
    console.log('the second')
}

test()

try-catch

// the first
// the second
async function test () { 
    console.log('the first')
    try {
        await Promise.reject()
    } catch (e) {

    }
    console.log('the second')
}

test()

2)多个await后面的异步操作,如果不存在继发关系,最好让它们同时触发

3)await仅能在async函数中使用

4)async函数返回值为一个promise对象

3. 异步任务顺序执行

a、b、c异步任务顺序执行代码如下:

async function thing(thingName) {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`执行${thingName}任务`)
            resolve()
        }, 1000)
    })
}

async function dothings () {
    await thing('a')
    await thing('b')
    await thing('c')
}

// 执行a任务
// 执行b任务
// 执行c任务
dothings()
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值