异步编程篇

64 阅读7分钟

异步编程篇


回调函数


function test (callack) {
  setTimeout(() => {
      let val = 'test'
     callback(val) 
  }, 500)
}

promise


promise.all()

Promise.all([promise 1, promise 2, promise 3]) 等待原则, 是在所有promise都完成后执行, 可以用于处理一些并发的任务,如果Promise实例都进入Fulfilled状态,Promise.all返回的实例才会变成Fulfilled状态并将Promise实实例数组的所有返回值组成一个数组,传给Promise.all返回实例的回调函数;如果有某一个或者多个实例进入rejected状态,Promise.all返回的实例会立即变成Rejected状态并将第一个rejected的实例返回值传递给Promise.all返回实例的回调函数。

const a = new Promise((resolve, reject) => {
            resolve('a')
        })
​
        const b = new Promise((resolve, reject) => {
            reject('b')
        })
        const c = new Promise((resolve, reject) => {
            reject('c')
        })
​
        const d = new Promise((resolve, reject) => {
            resolve('d')
        })
​
        const result = Promise.all([a, b,c,d]).then((res) => { console.log(res)}).catch((error) => { console.log(error)})
        
    // 如果有一个发生错误,则在catch哪里捕获到第一个错误的接口 然后await 获取的也是b
promise.race()

race() 接受的参数也是一个每项都是 promise的数组,当最先执行完的事件执行完之后,就直接返回该 promise对象的值。如果第一个 promise对象状态变成 resolved,那自身的状态变成了resolved;反之第一个 promise变成 rejected,那自身状态就会变成 rejected.

    const a = new Promise((resolve, reject) => {
           setTimeout(() => {
            resolve('a')
           }, 1500)
        })
​
        const b = new Promise((resolve, reject) => {
           setTimeout(() => {
            reject('b')
           }, 1000);
        })
        const c = new Promise((resolve, reject) => {
            reject('c')
        })
​
        const d = new Promise((resolve, reject) => {
            resolve('d')
        })
Promise.race([a,b]).then((res) => { }).catch((error) => { console.log(error)})
// reject b
        
        
Promise.race([a,b,c,d]).then((res) => { }).catch((error) => { console.log(error)}) // resolve b
​
​
Promise.race([b,b,c,d]).then((res) => { }).catch((error) => { console.log(error)}) // reject b
promise.finally()

finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
手写promise

兼容同步任务

完成了then的链式调用以后,我们再处理一个前边的细节,然后放出完整代码。上文我们说过,Promise的执行顺序是new Promise -> then()收集回调 -> resolve/reject执行回调,这一顺序是建立在executor是异步任务的前提上的,如果executor是一个同步任务,那么顺序就会变成new Promise -> resolve/reject执行回调 -> then()收集回调,resolve的执行跑到then之前去了,为了兼容这种情况,我们给resolve/reject执行回调的操作包一个setTimeout,让它异步执行。

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'class MyPromise {
    constructor(exxcutor) { // executor 是一个执行器,进入会立即执行
      this._status = PENDING
      this._value = undefined
      
      this._resolveQueue = [] // 成功队列
      this._rejectQueue = [] // 失败队列
      
       let _resolve = (val) => {
          const run = (run) => {
            if (this._status !== PENDING) return
           
            this._status = FULFILLED
            this._value = val
           
            while (this._resolveQueue.length) {
              const callback = this._resolveQueue.shift()
              callback(val)
             }
          }
          
           setTimeout(run)
       }
       
       let _reject = (val) => {
           const run = (val) => {
             if (this._status !== PENDING) return
             this._status = REJECTED
             this._value = val
           
             while (this._rejectQueue.length) {
               const callback = this._rejectQueue.shift()
               callback(val)
             } 
           }
         setTimeout(run) 
       }
       
       executor(_resolve, _reject)
    }
    
    then(resolveFn, rejectFn) {
        // 处理 .then() 方法为空的时候
        resolveFn = typeof resolveFn === 'function' : resolveFn : value => value
        rejectFn = typeof rejectFn === 'function' : rejectFn : reason => {
                    throw new Error(reason instanceof Error ? reason.message : reason)
                }
        return new MyPromse((resolve, reject) => {
            const fulfilledFn = val => {
               try {
                 let x = resolveFn(val)
                 x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
                } catch (error) {
                   reject(error)
               }
            }
            
            const rejectedFn = val => {
              try {
                  let x = rejectFn(val)  
              x instanceof MyPromise ? x.then(resolve, reject) : resolve(x) 
              } catch (error) {
                  reject(error)
              }
            }
            
            switch (this._status) {
                case PENDING:
                   this._resolveQueue.push(fulfilledFn)
                   this._rejectQueue.push(rejectedFn)
                    break;
                case FULFILLED:
                    fulfilledFn(this._value) 
                    break;
                 case REJECTED:
                     rejectedFn(this._value)
                     break;
            }
         })
    }
    
    catch (rejectFn) {
        return this.then(undefined, rejectFn )
    }
    
    resolve (value) {
       if (value instanceof MyPromise) return value
        return new MyPromise(resolve => resolve(value))
    }
    
    reject (error) {
       return new MyPromise((resolve, reject) => reject(error))
    }
    
    finally (callback) {
        return this.then(
            val => MyPromise.resolve(callback()).then(() => value),
            err =>  MyPromise.resolve(callback()).then(() => throw err)
        )
    }
    
    all (promiseArr) {
        let index = 0
        let result = []
        return new MyPromise((resolve, reject) => {
            promiseArr.forEach((p, i) => {
                MyPromise.resolve(p).then(val => {
                     index++
                    result[i] = val
                    if(index === promiseArr.length) {
                      resolve(result)
                    }
                }, err => {
                    reject(err)
                })
            })
        })
    }
    
    race(promiseArr) {
        return new MyPromise((resolve, reject) => {
            for (let p of promiseArr) {
        MyPromise.resolve(p).then(  //Promise.resolve(p)用于处理传入值不为Promise的情况
          value => {
            resolve(value)        //注意这个resolve是上边new MyPromise的
          },
          err => {
            reject(err)
          }
        )
      }
        })
    }
   
}

Generator

ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,通过next()方法可以切换到下一个状态,为改变执行流程提供了可能,从而为异步编程提供解决方案。

*/yieldasync/await看起来其实已经很相似了,它们都提供了暂停执行的功能,但二者又有三点不同:

  • async/await自带执行器,不需要手动调用next()就能自动执行下一步
  • async函数返回值是Promise对象,而Generator返回的是生成器对象
  • await能够返回Promise的resolve/reject的值
yield 修饰的内容,暂停恢复后会丢失
 // 定义函数
  function getName(){
    return 'ares5k'
  }
  // 定义生成器
  function * generator(){
    let name = yield getName()
    console.log(name)
  }
  let genIterator = generator() // 取得迭代器
  genIterator.next() // 执行到第一个 yield 后暂停
  genIterator.next() // 恢复执行
  // 输出:undefined

通过输出结果可以分析大概流程:

(1) 第一次调用 next 方法时,会在 let name = yield getName() 这一行发生暂停,赋值操作是从右到左,此时右侧执行完就已经暂停了,左侧赋值操作还未进行

(2) 第二次调用 next 方法,会恢复函数执行,继续执行 let name = yield getName() 的左侧赋值部分,而此时赋的并不是预期的 getName() 的结果, 而是一个 undefined,因此可以得出结论,yield 修饰的内容,暂停恢复后会丢失

解决 yield 修饰的内容丢失问题:

既然是在恢复时丢失了值,那么只要在恢复执行的那个 next 中,传入 yield 修饰的值即可,而 yield 修饰的内容, 会在前一个 next 暂停时以迭代器结果对象返回( IteratorResult 接口 ),具体实现如下:

 // 定义函数
  function getName(){
    return 'ares5k'
  }
  // 定义生成器
  function * generator(){
    let name = yield getName()
    console.log(name)
  }
  let genIterator = generator() // 取得迭代器
  const genIteratorResult = genIterator.next() // 取得迭代器结果对象
  genIterator.next(genIteratorResult.value) // 恢复执行,并传入 yield 修饰的内容
  // 输出:ares5k
解决回调地狱
// 定义函数
  let fn = word => {
    return new Promise(resolve => {
      setTimeout(function () {
        console.log(word)
        resolve()
      }, 500)
    })
  }
  // 业务的逻辑更紧凑,更易读,就像写同步代码一样
  function * generator() {
    yield fn('a')
    yield fn('b')
    yield fn('c')
    yield fn('d')
    yield fn('e')
  }
  // 按步骤执行各个任务,
  const genIterator = generator()
  genIterator.next().value.then(()=>{
    genIterator.next().value.then(()=>{
      genIterator.next().value.then(()=>{
        genIterator.next().value.then(()=>{
          genIterator.next()
        })
      })
    })
  })

可以看出,生成器 + Promise 实现的代码,业务部分的代码,逻辑更紧凑,更易读,就像写同步代码一样,但 是在生成器的调用阶段,就很麻烦,甚至又出现了回调地狱,而且调用多少次 next 都是预先定义好的,不灵活

实现生成器的自动执行

分析上面例子中的回调地狱,我们可以发现它与平时的回调地狱不同,它的回调内部没有很多的业务逻辑代码,仅 仅是调用 next 恢复函数执行而已,了解了这个点,我们就可以对其进行改造:

 // 定义函数
  let fn = word => {
    return new Promise(resolve => {
      setTimeout(function () {
        console.log(word)
        resolve()
      }, 500)
    })
  }
  // 业务的逻辑更紧凑,更易读,就像写同步代码一样
  function * generator() {
    yield fn('a')
    yield fn('b')
    yield fn('c')
    yield fn('d')
    yield fn('e')
  }
  // 生成器的自动执行器
  function auto(generator){
    function next(data){
      const result = genIterator.next(data)
      if (result.done) return result.value
      result.value.then(function(data){
        next(data)
      })
    }
    const genIterator = generator();
    next();
  }
  // 调用生成器的自动执行器
  auto(generator);

async await

async + await 是比使用生成器或 Promise 更简洁的回调地狱处理方案,也有人把其看作生成器 + Promise + 自执行的语法糖。

<script>
  // 场景一:函数执行异常
  const withError = async function () {
    throw Error
    return '异常后的内容不会被执行'
  }
  withError().catch(data => {
    console.log('场景一:函数执行异常时,会返回一个状态为 rejected 的 Promise 对象,异常原因:' + data)
  })
​
  // 场景二:未显式返回内容
  const nothing = async function () {
  }
  nothing().then(() => {
    console.log('场景二:未显式返回内容时,会返回一个状态为 fulfilled 的 Promise 对象,且处理程序无参')
  })
​
  // 场景三:显式返回 Promise 以外的值
  const notPromise = async function () {
    return '这是原返回值,现被当作参数传入处理程序'
  }
  notPromise().then(data => {
    console.log('场景三:显式返回 Promise 以外的值时,会返回一个状态为 fulfilled 的 Promise 对象,处理程序参数:' + data)
  })
​
  // 场景四:显式返回 状态为 fulfilled 的 Promise 对象
  const fulfilledPromise = async function () {
    return new Promise(resolve => {
      resolve('成功')
    })
  }
  fulfilledPromise().then(data => {
    console.log('场景四:显式返回 状态为 fulfilled 的 Promise 对象,直接返回该对象')
  })
​
  // 场景五:显式返回 状态为 rejected 的 Promise 对象
  const rejectedPromise = async function () {
    return new Promise((resolve, reject) => {
      reject('失败')
    })
  }
  rejectedPromise().catch(data => {
    console.log('场景五:显式返回 状态为 rejected 的 Promise 对象')
  })
</script>

await 修饰的内容:

① await 修饰 Promise 对象时: Ⅰ. Promise 对象必须调用 resolve 或 reject, 否则该 await 之后的代码不会执行 Ⅱ. await 修饰 Promise 语句之后的代码会一直等待,直到 Promise 调用完 resolve 或 reject 后在执行

② await 修饰非 Promise 对象时: Ⅰ. 不管 await 修饰的代码是否执行完成,之后的代码都会同步执行,不会等待

(2) await 场景列举,进一步熟悉 await 特点

① 场景一:await 标准执行顺序

await 修饰的内容执行 -> 主线程逻辑执行 -> async 函数剩余代码执行 -> setTimeout 执行

<script>
  setTimeout(function () {
    console.log(1)
  })
  const noResolveOrReject = async function () {
    await console.log(2)
    console.log(3)
    console.log(4)
  }
  noResolveOrReject()
  console.log(5)
  // 输出:2,5,3,4,1
</script>

② 场景二:await 修饰 Promise 对象,后面的代码会一直等待,直到 Promise 调用完 resolve 或 reject 后在执行 如果 await 前是赋值操作,那么 resolve 或 reject 的参数,会赋值给该变量

await 修饰的内容执行 -> 主线程逻辑执行 -> 回到 async 函数 -> 函数内剩余代码等待调用 resolve 或 reject 后执行

<script>
  const noResolveOrReject = async function () {
    const value = await new Promise(resolve => {
      console.log(1)
      setTimeout(() => resolve('会返回给 await 的数据'), 3000)
    })
    console.log(value)
    console.log(2)
    console.log(3)
  }
  noResolveOrReject()
  console.log(4)
  // 输出:1,4,会返回给 await 的数据,2,3
</script>

③ 场景三:await 修饰的 Promise 未调用 resolve 或 reject 语句,async 函数的剩余代码不会执行

await 修饰的内容执行 -> 主线程逻辑执行 -> 回到 async 函数 -> 函数内剩余代码不会执行

<script>
  setTimeout(function () {
    console.log(1)
  })
  const noResolveOrReject = async function () {
    await new Promise(() => console.log(2))
    console.log(3)
    console.log(4)
  }
  noResolveOrReject()
  console.log(5)
  // 输出:2,5,1
</script>

④ 场景四:await 修饰的是非 Promise 对象,不管其是否执行完,后面的代码都会同步执行

await 修饰的内容执行 -> 主线程逻辑执行 -> 回到 async 函数 -> 剩余代码不会等待 setTimeout 直接执行

<script>
  const noResolveOrReject = async function () {
    await setTimeout(() => console.log(1), 3000)
    console.log(2)
    console.log(3)
  }
  noResolveOrReject()
  console.log(4)
  // 输出:4,2,3,1
</script>
​
​

\