理解async await

109 阅读4分钟

理解async

async的执行原理其实就是自动执行generator函数,暂时不考虑genertor的编译步骤(比较复杂)。换言之,不考虑修饰符async、await的实现,只关注async的执行原理。

Generator (生成器)

Generator函数是ES6提供的一种异步编程的解决方案。

首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

官网给出的解释: 使用 function* 语法和一个或多个 yield 表达式以创建一个函数即为生成器,当然它的返回值就是一个迭代器即生成器

本质就是个函数:它不同于普通函数,是可以暂停执行的,所以函数名之前要加星号,以示区别。其实整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明。

调用 Generator 函数,会返回一个内部指针(即遍历器)g 。也就是说执行 Generator 函数返回的是指针对象。调用指针 g 的 next 方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield 语句;再次调用g的next的方法,内部指针指向下个yield语句。

next() :

每次调用 next 方法,会返回一个对象:

  • done 属性是一个布尔值,表示 Generator 函数是否执行完毕
  • value 属性是 yield 语句后面表达式的值,表示当前阶段的值

throw():

使用指针对象的 throw 方法抛出的错误,可以被函数体内的 try ... catch 代码块捕获。这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。

     function* gen(pramas) {
       try {
         
         yield "start form " + pramas;
         yield () => { return "doing form " + pramas };
         yield { label: "end form " + pramas };
 ​
         var y = yield x + 2; // 加入测试代码
 ​
       } catch (error) {
         console.log("gen throw " + error);
       }
       return "finally form " + pramas;
     }
     let obj = gen('cx');
     console.log(obj); // ƒ* gen(pramas)
     console.log(obj.next()); // {value: 'start form cx', done: false}
     console.log(obj.next()); //{done: false, value: ƒ}
     console.log(obj.next()); // {value: {…}, done: false}
     console.log(obj.next()); // {value: 'finally form cx', done: true}
     console.log(obj.next()); //{value: undefined, done: true}
     console.log(obj.throw("出错了")); 
     // gen throw ReferenceError: x is not defined
     // Uncaught 出错了

async函数的实现

示例

 const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))
 ​
 async function test() {
   const data = await getData()
   console.log('1: ', data);
   const data2 = await getData()
   console.log('2: ', data2);
   return 'success'
 }
 ​
 // 这样的一个函数 应该再1秒后打印data 再过一秒打印data2且打印success
 test().then(res => console.log(res))

如果我们把它用generator函数表达,会是怎么样的呢?

第一次调用next,其实只是停留在了yield getData()这里,data的值并没有被确定。下一次调用next的时候,传的参数会被作为上一个yield前面接受的值

 const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))
 ​
 function* testG() {
   // await被编译成了yield
   const data1 = yield getData()
   console.log('data: ', data1);
   const data2 = yield getData()
   console.log('data2: ', data2);
   return 'success'
 }
 ​
 let gen = testG()
 ​
 let dataPromise = gen.next().value; // 第一次调用`next`, data1 = undefined
 ​
 dataPromise.then((value1) => {
     let data2Promise = gen.next(value1).value // 再次调用`next`,将参数作为上一个yield的返回值, data1 = value1的值
     // 此时就会打印出data;console.log('data: ', data);
     
     data2Promise.then((value2) => {
          gen.next(value2) // 再次调用`next`,将参数作为上一个yield的返回值, data2 = value2的值
         // 此时就会打印出data2,console.log('data2: ', data2);
     })
 })
 ​

思路

generator函数是不会自动执行的,每一次调用它的next方法,会停留在下一个yield的位置。

利用这个特性,我们只要编写一个自动执行的函数,就可以让这个generator函数完全实现async函数的功能。

 function asyncToGenerator(generatorFunc) {
   return () => {
     // 先调用generator函数 生成迭代器 对应 var gen = testG()
     const gen = generatorFunc
 ​
     // 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
     // let test = asyncToGenerator(testG)
     // test().then(res => console.log(res))
     return new Promise((resolve, reject) => {
 ​
       // 内部定义一个step自动执行的函数,
       // key有next和throw两种取值,分别对应了gen的next和throw方法
       // arg参数则是用来把promise resolve出来的值交给下一个yield
       function step(key, arg) {
         let generatorResult
         try {
           generatorResult = gen[key](arg)
         } catch (error) {
           return reject(error)
         }
 ​
         const {value, done} = generatorResult
         if (done) { // 执行完毕,直接返回
           return resolve(value);
         } else { // 执行下一个yield,直接返回
           return Promise.resolve(value).then(val => step('next', val), err => step('throw', err))
         }
       }
 ​
       // 首次进入会执行一次next(), 此时value = undefined,指针指向第二个yield
       step("next");
     })
   }
 }
 ​

案例

 import asyncToGenerator from "@/kits/UiKit"; 
  
 // 模拟请求
  const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))
 ​
 // 原来我们这样写async函数
 async function test() {
   const data1 = await getData()
   console.log("1==>",data1);
   const data2 = await getData()
   console.log("2==>",data2)
   return data2
 }
 ​
 // 用我们实现的async函数
 function* testG() { // async函数会被编译成generator函数 (babel会编译成更本质的形态,这里我们直接用generator)
   // await被编译成了yield
   const data1 = yield getData()
   console.log("1==>",data1);
   const data2 = yield getData()
   console.log("2==>",data2)
   return data2
 }
 ​
 //test().then(res=>{
 //  console.log("finally==>",res);
 //})
 ​
 asyncToGenerator(testG)().then(res => {
    console.log("finally==>",res);
 })
 ​
 // 1==> data
 // 2==> data
 // finally==> data


最后一句

学习心得!若有不正,还望斧正。希望掘友们不要吝啬对我的建议。