系统化学习前端之JavaScript(ES6:异步编程)

前言

JavaScript 异步编程这块东西比较多,涉及到宏任务和微任务,所以单开一个篇幅梳理一下。

同步和异步

同步和异步是一种宏观概念,具体表现在 JavaScript 中,是同步任务和异步任务,即同步函数和异步函数。

同步

同步指函数在 JavaScript 同步执行。

同步函数执行过程:A 函数进入函数调用栈,等待 A 函数执行完成后,B 函数进入函数调用栈,等待 B 函数执行完成后,C 函数...

异步

异步指函数在 JavaScript 异步执行。

异步函数执行过程:A 函数进入函数调用栈,判断 A 函数是否为同步函数,是则等待 A 函数执行完成后,B 函数进入函数调用栈,判断 B 函数是否为同步函数,否则添加至任务队列。

注意:任务队列根据 "执行者" 不同可以分为微任务队列和宏任务队列,具体可参考 V8 中异步回调

JavaScript 执行流程

JavaScript 执行函数是通过调用栈进行的,按照全局作用域中的函数顺序(先进后出)依次进入函数调用栈执行。

JavaScript 函数执行顺序

  1. 函数调用栈依次执行全局作用域中的函数任务。

  2. 同步函数任务,函数调用栈会依次执行完成。

  3. 同步函数任务执行完成,函数调用栈会执行微任务队列中的微任务(微任务也是函数任务,只不过是回调函数)。

  4. 执行完微任务队列以后,函数调用栈会执行宏任务队列中的宏任务(宏任务也是回调函数)。

异步编程

JavaScript 是单线程语言,多任务执行必须借助异步编程来实现,而 JavaScript 对异步编程的实现就是回调函数。将一个任务拆分两个部分,执行函数执行一部分,回调函数执行另一部分。

回调函数:函数作为参数传入另一个函数,则该函数称为回调函数,被传参函数为执行函数。

回调地狱

了解了 JavaScript 函数的执行顺序:同步函数 --> 微任务 --> 宏任务。微任务和宏任务其实都是回调函数,那又如何保证回调函数能按照指定顺序执行呢?或者说,一个异步任务拆分成 A,B,C三个部分,该如何保证 A,B,C 顺序执行呢?

常规方法是在回调中调用下一个回调函数,保证顺序执行。如:

setTimeout(function() {
    A()
    setTimeout(function() {
        B()
        setTimeout(function () {
            C()
        }, 300)
    }, 200)
}, 100)

上述代码可以保证 A, B, C 三个任务顺序执行,但是,如果存在多个任务呢?那就形成了多层回调函数嵌套而成的回调地狱。

回调地狱会带来阅读性差,难以维护等问题,因此,Promise 应运而生。

Promise

Promise 是允许将回调函数的嵌套,改成链式调用的方案。Promise 对象内部封装了处理异步操作的 API,可以将异步操作以同步操作的流程表达出来。

  1. Promise 对象

    实例化 Promise 对象

    let promise = new Promise(function (resolve, reject) {
        // 异步任务的第一个部分操作
        setTimeout(function () {
            let res = A()
            if(res == success) {
                resolve(successValue)
            } else {
                reject(errorValue)
            }
        })
    })
    

    Promise 是一个构造函数,接收一个回调函数,回调函数的参数是 resolvereject 方法,这两个参数是内置方法,由 JavaScript 引擎提供。

    实例化过程中,参数回调函数会立即执行,同时执行异步任务第一部分 A() 操作,根据 A() 返回结果:成功执行 resolve 方法,失败则执行 reject 方法。

    实例 promise 对象内部保存三个状态,分别是:pending, fulfilledrejected。根据实例化过程中,A() 返回结果不同则状态不同:若 A() 返回失败或抛出错误,则 promise 状态会由 pending 变为 rejected,反之,则由 pending 变为 fulfilled

    实例 promise 对象内部也保存一个结果值,当 fulfilled 状态时,会存在 resolve 方法的参数值 successValue,当 rejected 状态时,会保存 reject 方法的参数值。

    • pending --> fulfilled

      function sum(x, y) {
          return x + y
      }
      
      let promise = new Promise(function(resolve, reject) {
          console.log('立即执行了回调函数')
          setTimeout(function () {
              let res = sum(1,1)
              if(res === 2) {
                  resolve('success')
              } else {
                 reject('fail')
              }
          }, 1000)
      })
      
      console.log(promise)
      
    • pending --> rejected

      function sum(x, y) {
          return x + y
      }
      
      let promise = new Promise(function(resolve, reject) {
          console.log('立即执行了回调函数')
          setTimeout(function () {
              let res = sum(1,2)
              if(res === 2) {
                  resolve('success')
              } else {
                  reject('fail')
              }
          }, 1000)
      })
      
      console.log(promise)
      
  2. Promise 原型方法

    原型方法是实例可以直接调用的。

    • then

      then 方法接收两个回调函数作为参数,第一个回调函数为成功回调函数;第二个回调函数为失败回调函数,可以缺省。

      promise 实例的状态为 fulfilled ,则执行第一个回调函数,回调函数的参数为 resolve 方法的参数,也是 promise 的结果值。

      function sum(x, y) {
          return x + y
      }
      
      let promise = new Promise(function(resolve, reject) {
          console.log('立即执行了回调函数')
          setTimeout(function () {
              let res = sum(1,1)
              if(res === 2) {
                  resolve('success')
              } else {
                  reject('fail')
              }
          }, 1000)
      })
      
      console.log(promise)
      
      let promise1 = promise.then(function(res) {
          console.log(res)
          return 3
      })
      
      console.log(promise1)
      

      promise 实例的状态为 rejected,则执行第二个回调函数,回调函数的参数为 reject 方法的参数,同样是 promise 的结果值。

      function sum(x, y) {
          return x + y
      }
      
      let promise = new Promise(function(resolve, reject) {
          console.log('立即执行了回调函数')
          setTimeout(function () {
              let res = sum(1,2)
              if(res === 2) {
                  resolve('success')
              } else {
                  reject('fail')
              }
          }, 1000)
      })
      
      console.log(promise)
      
      let promise1 = promise.then(function(res) {
          console.log(res)
          return 3
      }, function(err) {
          console.log(err)
          return 0
      	// throw new Error('err')
      })
      
      console.log(promise1)
      

      注意:

      a. promise.then() 会返回一个 promise

      b. 返回的 promise 的状态和结果值可以根据 then 的回调函数返回值决定;

      c. 有返回值,则返回值作为 promise 的结果值;

      d. 无返回值,则 promise 的结果值为 undefined;

      e. 状态有无返回值均为 fulfilled,只有当回调函数中抛出错误,状态才会变为 rejected

    • catch

      then 方法缺省第二个参数时,可以使用 catch 方法,该方法接收一个回调函数,同 then 方法第二个参数。当 promise 的状态为 rejected 或者异步操作中抛出错误均会被 catch 方法捕获,回调函数可以获取错误信息。同样,catch 方法也返回一个 promise

      function sum(x, y) {
          return x + y
      }
      
      let promise = new Promise(function(resolve, reject) {
          console.log('立即执行了回调函数')
          setTimeout(function () {
              let res = sum(1,2)
              if(res === 2) {
                  resolve('success')
              } else {
                  reject('fail')
              }
          }, 1000)
      })
      
      console.log(promise)
      
      let promise1 = promise.then(function(res) {
          console.log(res)
          return 3
      }).catch(function(err) {
          console.log(err)
          return 0
      })
      
      console.log(promise1)
      
    • finally

      无论 promise的状态是 fulfilled 还是 rejected 都会执行 finally 方法的回调函数,类似 try...catch...finally

      function sum(x, y) {
          return x + y
      }
      
      let promise = new Promise(function(resolve, reject) {
          console.log('立即执行了回调函数')
          setTimeout(function () {
              let res = sum(1,2)
              if(res === 2) {
                  resolve('success')
              } else {
                  reject('fail')
              }
          }, 1000)
      })
      
      console.log(promise)
      
      let promise1 = promise.then(function(res) {
          console.log(res)
          return 3
      }).catch(function(err) {
          console.log(err)
          return 0
      }).finally(function() {
          console.log('well done')
      })
      
      console.log(promise1)
      

    注意:thencatch 会返回一个 promise,因此 promise 可以链式调用,如 promise.then().then().then().catch().finally()。同样,catch 置于链式调用的最后,可以捕获之前 promise 或者 then 返回的 promiserejected 状态或抛出的异常,这种情况称之为异常穿透。

  3. Promise 静态方法

    • all

      Promise.all 接收多个 promise 对象构成的数组,表示同时执行多个 promise 对象中异步操作,返回一个 promise 对象,返回 promise 对象的状态和结果值由 promise 数组决定。

      当多个 promise都执行完成后,返回 promise 均为 fulfilled 时,则 all 返回的 promise 的状态为 fulfilled,结果值为参数数组中 promise 结果值构成的数组。

      function sum(x, y) {
          return x + y
      }
      
      let p2 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(1,1)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p2 fail')
              }
          })
      })
      
      let p3 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(1,2)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p3 fail')
              }
          })
      })
      
      let p4 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(1,3)
              if(res > 1) {
              resolve(res)
              } else {
              reject('p4 fail')
              }
          })
      })
      
      let promise = Promise.all([p2,p3,p4])
      
      console.log(promise)
      

      当多个 promise 中有一个执行完成,并返回 rejected 状态时,则 all 返回当前 promise 对象,即第一个执行完成且返回 rejectedpromise 对象。

      function sum(x, y) {
          return x + y
      }
      
      let p0 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(0,0)
             if(res > 1) {
                  resolve(res)
              } else {
                  reject('p0 fail')
              }
          }, 200)
      })
      
      let p1 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(1,0)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p1 fail')
              }
          }, 100)
      })
      
      let p2 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(1,1)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p2 fail')
              }
          })
      })
      
      let promise = Promise.all([p0,p2,p1])
      
      console.log(promise)
      
    • race

      Promise.race() 接收多个 promise 对象构成的数组,表示同时执行多个 promise 对象中异步操作,返回一个 promise 对象,返回 promise 对象为多个 promise 中第一个执行完成并返回的 promise 对象。

      function sum(x, y) {
          return x + y
      }
      
      let p0 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(0,0)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p0 fail')
              }
          }, 200)
      })
      
      let p1 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(1,0)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p1 fail')
              }
          }, 100)
      })
      
      let p2 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(1,1)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p2 fail')
              }
          }, 50)
      })
      
      let promise = Promise.race([p0,p2,p1])
      
      console.log(promise)
      
    • any

      Promise.anyPromise.all 相对应,接收多个 promise 对象构成的数组,表示同时执行多个 promise 对象中异步操作,返回一个 promise 对象,返回 promise 对象的状态和结果值由 promise 数组决定。

      当多个 promise都执行完成后,返回 promise 均为 rejected 时,则 any 返回的 promise 的状态为 rejected,结果值为参数数组中 promise 结果值构成的数组。

      function sum(x, y) {
          return x + y
      }
      
      let p0 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(0,0)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p0 fail')
              }
          }, 200)
      })
      
      let p1 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(1,0)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p1 fail')
              }
          }, 100)
      })
      
      let promise = Promise.any([p0,p1])
      
      console.log(promise)
      

      当多个 promise 中有一个执行完成,并返回 fulfilled 状态时,则 any 返回当前 promise 对象,即第一个执行完成且返回 fulfilledpromise 对象。

      function sum(x, y) {
          return x + y
      }
      
      let p0 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(0,0)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p0 fail')
              }
          }, 200)
      })
      
      let p1 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(1,0)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p1 fail')
              }
          }, 100)
      })
      
      let p2 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(1,1)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p2 fail')
              }
          }, 300)
      })
      
      let promise = Promise.any([p0,p2,p1])
      
      console.log(promise)
      
    • allSettled

      Promise.allSettled() 接收多个 promise 对象构成的数组,表示同时执行多个 promise 对象中异步操作,当所有异步操作均完成以后,返回一个 promise 对象,返回 promise 对象状态一直为 fulfilled,结果值为多个 promise 状态结果对象组成的数组。

      function sum(x, y) {
          return x + y
      }
      
      let p0 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(0,0)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p0 fail')
              }
          }, 200)
      })
      
      let p1 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(1,0)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p1 fail')
              }
          }, 100)
      })
      
      let p2 = new Promise(function (resolve, reject) {
          setTimeout(function () {
              let res = sum(1,1)
              if(res > 1) {
                  resolve(res)
              } else {
                  reject('p2 fail')
              }
          }, 300)
      })
      
      let promise = Promise.allSettled([p0,p2,p1])
      
      console.log(promise)
      
    • resolve

      Promise.resolve 接收一个参数,可以将参数转化为 promise 对象并返回。

      当不传参时,返回一个 promise 对象,状态为 fulfilled,结果值为 undefined

      当参数为 promise 对象,则直接返回参数 promise 对象。

      当参数为 JavaScript 数据类型,则返回一个状态为 fulfilled,结果值为参数的 promise 对象。

      当参数为 thenable 对象,则返回一个 promise,并立即执行对象的 then 方法。

      let thenable = {
          then: function(resolve, reject) {
              resolve(42);
          }
      };
      
      let p1 = Promise.resolve(thenable);
      p1.then(function (value) {
            console.log(value);  // 42
      });
      
    • reject

      Promise.reject 接收一个参数,会返回一个状态为 rejected,结果值为参数的 promise 对象,不传参,则结果值为 undefiend

Generator

Generator 是 JavaScrip 异步编程的又一方案。Generator 函数是一个状态机,封装了多个内部状态,通过返回一个生成器对象来保存状态,利用 API 手动获取状态,异步读取结果。

  1. 生成器函数

    Generator 函数又被称为生成器函数,是一种特殊的函数。相比于普通函数,主要标志有两点:*yield

    function* gen() {
        yield 'generator start'
        yield 'generator'
        return 'generator end'
    }
    
    let g = gen()
    console.log(g.next()) // {value: 'generator start', done: false}
    console.log(g.next()) // {value: 'generator', done: false}
    console.log(g.next()) // {value: 'generator end', done: true}
    console.log(g.next()) // {value: undefined, done: true}
    

    Generator 函数通过 yieldreturn 关键字定义状态,调用 Generator 函数会返回一个可迭代的生成器对象。通过原型方法或者遍历可以读取生成器对象保存的状态。

    注意:return 也可以定义状态,但一定是定义最后一个状态。yield 只能用于生成器函数,用于普通函数会抛出错误。

  2. 原型方法

    • next

      g.next() 方法每调用一次可以读取一次生成器对象 g 中的状态,当状态读取完毕,依旧调用会返回 {value: undefined, done: true}

      g.next() 接收参数实际是向生成器函数传值,可以通过上一次的 yield 的返回值获取数据。

      function* fun() {
          yield 'generator start'
          let res = yield 'generator'
          yield res
          return 'generator end'
      }
      
      let f = fun()
      console.log(f.next()) // {value: 'generator start', done: false}
      console.log(f.next()) // {value: 'generator', done: false}
      console.log(f.next('step three')) // {value: 'step three', done: false}
      console.log(f.next()) // {value: 'generator end', done: true}
      
    • return

      g.return 方法调用是结束生成器对象 g 的状态读取,不传参则读取结果为 {value: undefined, done: true},传参则 value 为参数值

      function* fun() {
          yield 'generator start'
          yield 'generator'
          return 'generator end'
      }
      
      let f = fun()
      console.log(f.next()) // {value: 'generator start', done: false}
      console.log(f.return('intercept')) // {value: 'intercept', done: true}
      console.log(f.next()) // {value: undefined, done: true}
      
  3. 异步操作同步化

    异步任务分为 A,B,C三个部分,顺序执行 A(), B(), C()

    function* gen() {
        yield A()
        yield B()
        yield C()
    }
    
    let g = gen()
    g.next()
    g.next()
    g.next()
    
  4. co 模块

    Generator 函数调用需要手动调用,如 g.next()。co 模块可以自动化执行 Generator 函数。

    const co = require('co')
    function* gen() {
        yield 'generator'
    }
    co(gen)
    

async 和 await

async 和 await 是 Generator 函数的语法糖。但实际上功能和语义远强于 Generator 函数。

  1. 异步函数

    异步函数是指使用 async 标记的函数,表示存在异步操作,await后接异步操作的 promise 对象,非 promise 对象,会通过 Promise.resolve处理得到 promise 对象。

    async function fun() {
        let res = await Promise.resolve(1)
        console.log(res) // 1
        return 2
    }
    
    let f = fun()
    console.log(f) // Promise<fulfilled, 2>
    

    await 相当于 promise.then可以获取 fulfilled 状态下 promise 的结果值。

    注意:异步函数会返回一个 promise 对象,结果值为异步函数返回值,状态在异步函数抛出错误时为 rejected,反之为 fulfilled

  2. 错误处理

    await 只能获取 fulfilled 状态下的结果值,无法拦截 rejected 的异常。需要借助 try...catch 进行错误处理。

    async function fun() {
        try {
            let res = await Promis.reject('fail')
        } catch(err) {
            console.log(err) // fail
        }
    }
    
    fun()
    

后记

ES6 拓展的异步编程处理方案,主要包括 Promise,Generator以及async,await,这些异步操作都是程序为执行者处理的,是微任务。而 JavaScript中通过浏览器线程处理的,如定时器,事件监听,网络请求等,是宏任务。

posted @ 2023-04-04 17:33  深巷酒  阅读(48)  评论(0编辑  收藏  举报