async、await运用原理

前面我们讲到了宏任务和微任务,我们先来回顾一下:

JavaScript 异步编程

由于 JavaScript 是单线程执行模型,因此必须支持异步编程才能提高运行效率。异步编程的语法目标是让异步过程写起来像同步过程。

运行机制

  1. 在执行栈中执行一个宏任务。
  2. 执行过程中遇到微任务,将微任务添加到微任务队列中。
  3. 当前宏任务执行完毕,立即执行微任务队列中的任务。
  4. 当前微任务队列中的任务执行完毕,检查渲染,GUI线程接管渲染。
  5. 渲染完毕后,js线程接管,开启下一次事件循环,执行下一次宏任务(事件队列中取)。

我们常常采用Promise来将回调函数改成链式调用,如果不转换,可能多个回调函数嵌套最终形成回调地狱,值得强调的是,即使采用了Promsie,但最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆 then,原来的语义变得很不清楚。

const fs = require('fs')

const readFileWithPromise = file => {
  return new Promise((resolve, reject) => {
    fs.readFile(file, (err, data) => {
      if (err) {
        reject(err)
      } else {
        resolve(data)
      }
    })
  })
}

readFileWithPromise('/etc/passwd')
  .then(data => {
    console.log(data.toString())
    return readFileWithPromise('/etc/profile')
  })
  .then(data => {
    console.log(data.toString())
  })
  .catch(err => {
    console.log(err)
  })

这个时候为了解决Promise的问题,ES7中async和await就被提出来了。

async、await

先说一下async的用法,它作为一个关键字放在函数前面,用于表示函数是一个异步函数,因为async就是异步的意思,异步函数意味着该函数的执行不会阻塞后面代码的运行。

const fs = require('fs')
async function readFile() {
  try {    
    var f1 = await readFileWithPromise('/etc/passwd')
    console.log(f1.toString())
    var f2 = await readFileWithPromise('/etc/profile')
    console.log(f2.toString())
  } catch (err) {
    console.log(err)
  }
}

async和await 函数写起来跟同步函数一样,条件是需要接收 Promise 或原始类型的值。异步编程的最终目标是转换成最容易理解的形式。

分析 async、await 实现原理之前,先介绍下知识:

Generator

Generator是ES6标准引入的新的数据类型。Generator可以理解为一个状态机,内部封装了很多状态,同时返回一个迭代器Iterator对象。可以通过这个迭代器遍历相关的值及状态。
Generator的显著特点是可以多次返回,每次的返回值作为迭代器的一部分保存下来,可以被我们显式调用。

Generator 的基本用法:

function* foo(x) {
    yield x + 1;
    yield x + 2;
    return x + 3;
}
const result = foo(0) // foo {<suspended>}
result.next();  // {value: 1, done: false}
result.next();  // {value: 2, done: false}
result.next();  // {value: 3, done: true}
result.next();  //{value: undefined, done: true}

在这个例子里,我们可以看到,在执行foo函数后返回了一个 Generator函数的实例。它具有状态值suspended和closed,suspended代表暂停,closed则为结束。但是这个状态是无法捕获的,我们只能通过Generator函数的提供的方法获取当前的状态。 在执行next方法后,顺序执行了yield的返回值。返回值有value和done两个状态。value为返回值,可以是任意类型。done的状态为false和true,true即为执行完毕。在执行完毕后再次调用返回{value: undefined, done: true}

注意:在遇到return的时候,所有剩下的yield不再执行,直接返回{ value: undefined, done: true }

Generator函数的方法

Generator函数提供了3个方法:next / return / throw

  1. next方式是按步执行,每次返回一个值,同时也可以每次传入新的值作为计算
  2. return则直接跳过所有步骤,直接返回 {value: undefined, done: true}
  3. throw则根据函数中书写try catch返回catch中的内容,如果没有写try,则直接抛出异常。

run函数

有了前面关于 Generator 知识的复习,我们实现本文的核心函数;
它接收一个 Generator 函数 fn 作为参数,它的功能就是模拟 async/await 机制运行该 fn 函数。

function run(fn) {
    const gen = fn()
    function _next(data){
        const { value, done } = gen.next(data)
        if(done) return value

        if(value.then){
            value.then(data => _next(data))
        }else{
            _next(value)
        }
    }
    _next()
}

看了这个示例代码,应该就能明白 run() 的作用了,实际上通过这个函数,我们已经模拟实现了 async/await 机制;

我们做个不严谨的类比来说明:

  • function * work() { } 中间的 * 号就相当于 async 关键字的作用;
  • yield 关键字就相当于 await 关键字;

实际上 async/await 关键字也可以认为是 ES7 的语法糖,我们已经通过 run() 函数来模拟了其工作原理,所以这个时候它的基本原理也显而易见了。

async&await实现原理

  • async(异步)

    • 自动将常规函数转换成Promise,返回值也是一个Promise对象,Promise对象的状态值是resolved(成功的)
  • await(等待)

    • 只能放在async函数里,await 强制后面的代码等待,直到Promise对象resolve,得到resolve的值作为await表达式的运算结果 。
  • 使用async/await的时候,是无法捕获错误的,任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行,如果想捕获错误,需要使用es5中的try&catch,来进行错误的捕获 。

  • async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。

使用async&await实现异步请求

想要Generator函数执行下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式或return语句。由此可见,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值