【异步】js中异步的实现方式 async await /Promise / Generator

JS的异步相关知识

js里面一共有以下异步的解决方案

传统的回调

省略 。。。。

生成器

Generator 函数是 ES6 提供的一种异步编程解决方案, 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

怎么定义一个生成器

复制代码
function* gen1(){
   yield 1;
   yield 2;
}

function* test() {
  yield 'a';
}

test.next(); // value: "a", done: false}

调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)。
每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。   
value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值; 
done属性是一个布尔值,表示是否遍历结束

function* 在 JavaScript 中不是 函数指针,而是一种特殊的函数语法,叫做生成器函数(Generator Function)

function* 是什么?

function* 用于定义一个生成器函数 。生成器函数是一种可以暂停和恢复执行 的特殊函数。它返回一个生成器对象(Generator Object) ,这个对象实现了迭代器(Iterator)协议。

Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。

异步操作需要暂停的地方,都用 yield 语句注明

调用 Generator 函数,返回的就是一个生成器(迭代器)对象。

然后调用迭代器对象的 next() 方法,就是按顺序执行代码,知道碰到下一个yield 关键字 ,然后返回,然后要可以执行next() 直到函数体内部流程执行完毕。

next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法,会返回一个对象,表示当前阶段的信息( value 属性和 done 属性)。value 属性是 yield 语句后面表达式的值,表示当前阶段的值;done 属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。

如:

const gen1 = myGenerator(); // 返回一个生成器对象

result=gen1.next()

console.log(result.value,result.done); //如果没有执行完成的话 done是false

了解generator用法

复制代码
function* Generator() {
  yield "1";
  yield Promise.resolve(2);
  return "3";
}
var gen = Generator();
console.log(gen); //返回一个指针对象 Generator {<suspended>}

调用next方法

复制代码
let res1 = gen.next();
console.log(res1); // 返回当前阶段的值 { value: '1', done: false }

let res2 = gen.next();
console.log(res2); // 返回当前阶段的值 { value: Promise { 2 }, done: false }

res2.value.then((res) => {
  console.log(res); // 2
});

let res3 = gen.next();
console.log(res3); // { value: '3', done: true }

let res4 = gen.next();
console.log(res4); // { value: undefined, done: true }

async/await实现

而async/await 是**Generator+Promise方案的语法糖。**async 函数的实现,就是将 Generator 函数和自动执行器(自动调用生成器的next()方法,直到最后一个),包装在一个函数里。

generator 函数需要通过调用next()方法,才能往后执行到下一个yield,但是 async 函数却不需要,它能够自动向后执行。

await 可以理解为是 async wait 的简写。await 必须出现在 async 函数内部,不能单独使用

await 字面上使得 JavaScript 等待,直到 promise 处理完成,然后将结果继续下去。

await不能工作在顶级作用域,需要将await代码包裹在一个async函数中

也可以形象的理解: 当执行异步函数时,碰到await时就把这个await 后面的所有代码全部封装成一个promise对象,然后放在微任务队列中等待调用,这样也就类似await后面的代码都必须等待

await后面的一行代码执行完毕。依次这样处理异步函数中的其他代码。

async/await的理解

  • async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里
  • async 函数执行结果返回的是一个Promise
  • async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成await
  • async/await 就是 Generator 的语法糖,其核心逻辑是迭代执行next函数
async 函数的实现1

async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里

复制代码
//第一种
async function fn(args){
  // ...
}

//第二种 等同于 一
function fn(args){ 
  return spawn(function*() {
    // ...
  }); 
}

所有的 async 函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。

下面给出 spawn 函数的实现,基本就是前文自动执行器的翻版。

复制代码
function spawn(genF) {
  return new Promise(function(resolve, reject) {
    var gen = genF(); //执行器
    function step(nextF) {
      try {
        var next = nextF();
      } catch(e) {
        return reject(e); 
      }
      if(next.done) {
        return resolve(next.value);
      } 
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });      
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}
综合上面的代码的完整实例
复制代码
<html>
    <head>
        <title>Test</title>
    </head>
    <body>
        <div id="container"></div>
    </body>
    <script>
        function testfun(){
            console.log('testfun')
        }
        function fn(args){ 
            return spawn(function*() {
                yield testfun();
                yield testfun();
            }); 
        }
       function spawn(genF) {
            return new Promise(function(resolve, reject) {
                var gen = genF(); //执行器
                function step(nextF) {
                    try {
                        var next = nextF();
                    } catch(e) {
                        return reject(e); 
                    }
                    if(next.done) {
                        //如果没有其他迭代了 就把最开始的Promise对象的状态设置为ffulfilled,然后返回结果
                        //resolve 返回结果会传递给then方法中的onFulfilled函数,也就是这里的next.value
                        return resolve(next.value);
                    } 
                    //当前迭代器 没有执行完毕 就继续抛出一个Promise对象,然后继续执行迭代器中的下一个yield表达式
                    //直到所有的yield表达式都执行完毕

                    Promise.resolve(next.value).then(function(v) {
                        step(function() { return gen.next(v); });      
                    }, function(e) {
                        step(function() { return gen.throw(e); });
                    });
                }
                step(function() { return gen.next(undefined); });
            });
        }
        fn()
    </script>
</html>
其他实例分析
复制代码
// Generator 函数,依次读取两个文件。

const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
==================================
写成async函数,就是下面这样。

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

async函数对 Generator 函数的改进
  • 当一个 async 函数中有多个 await命令时,如果不想因为一个出错而导致其与的都无法执行,应将await放在try...catch语句中执行

    async function testAwait () {
    try {
    await func1()
    await func2()
    await func3()
    } catch (error) {
    console.log(error)
    }
    }

  • 并发执行 await 命令: await 是顺序执行的,Promise.all() 是并行的

    // 方法 1
    let [res1, res2] = await Promise.all([func1(), func2()])

    // 方法 2
    let func1Promise = func1()
    let func2Promise = func2()
    let res1 = await func1Promise
    let res2 = await func2Promise

  • async方法: 一个class方法同样能够使用async,只需要将async放在它之前就可以

就像这样:

复制代码
class Waiter {
   async wait () {
       return await Promise.resolve(1)
   }
}
new Waiter().wait().then(alert) // 1

=======================

# Class methods:
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jaffathecake').then(...);
  • sleep(): 让线程休眠一段时间

    // wait ms milliseconds
    function wait(ms) {
    return new Promise(r => setTimeout(r, ms));
    }

    async function hello() {
    await wait(500);
    return 'world';
    }

业务场景举例

你有三个请求需要发生,第三个请求是依赖于第二个请求的解构第二个请求依赖于第一个请求的结果。若用 ES5实现会有3层的回调,若用Promise 实现至少需要3个then。一个是代码横向发展,另一个是纵向发展。给出 async-await 的实现

复制代码
//我们仍然使用 setTimeout 来模拟异步请求
function sleep(second, param) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(param);
        }, second);
    })
}

async function test() {
    let result1 = await sleep(2000, 'req01');
    let result2 = await sleep(1000, 'req02' + result1);
    let result3 = await sleep(500, 'req03' + result2);
    console.log(`
        ${result3}
        ${result2}
        ${result1}
    `);
}

test();

Promise的基本原理

介绍

Promise 是异步编程的一种解决方案,比传统的解决方案------回调函数和事件------更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。Promise的异步在js中是通过微任务的方式构建的,Promise的.then() 会触发生成一个微任务,放在队列中。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

  • 首先在new Promise时,需要传入一个回调函数executor函数,Promise的主要业务流程都在executor函数中执行。
  • 如果运行在excutor函数中的业务执行成功了,会调用resolve(手动调用)函数;如果执行失败了,则调用reject函数。
  • Promise的状态不可逆,同时调用resolve函数和reject函数,默认会采取第一次调用的结果。
  • 它有constructor构造函数,还有catchfinallythen三个方法
  • 首先我们在调用Promise时,会返回一个Promise对象。

Promise/A+的规范比较多,在这列出一下核心的规范。Promise/A+规范

  • promise有三个状态:pending,fulfilled,rejected,默认状态是pending。
  • promise有一个value保存成功状态的值,有一个reason保存失败状态的值,可以是undefined/thenable/promise。
  • promise只能从pending到rejected, 或者从pending到fulfilled,状态一旦确认,就不会再改变。
  • promise 必须有一个then方法,then接收两个参数,分别是promise成功的回调onFulfilled, 和promise失败的回调onRejected。
  • 如果then中抛出了异常,那么就会把这个异常作为参数,传递给下一个then的失败的回调onRejected。

知识点1

复制代码
new Promise((resolve, reject) => {

   console.log("tst111")
})

///Promise 对象创建的时候 传入的参数 函数 会被同步执行,就是说在当前宏任务同步执行。

知识点2

复制代码
const resolvePromise = Promise.resolve(1); //立刻返回一个fulled状态的Promise
//执行then 产生一个微任务
resolvePromise.then((res) => console.log(res)); 
console.log('test');

先输出 test 
然后是1

其他学习记录:

https://blog.csdn.net/binqian/article/details/145742026?spm=1011.2415.3001.5331

https://blog.csdn.net/binqian/article/details/145737399?spm=1011.2415.3001.5331

https://blog.csdn.net/binqian/article/details/145658887?spm=1011.2415.3001.5331

参考:

3.3.1 Async---Await原理 | 大前端

相关推荐
浩星4 分钟前
iframe引入界面有el-date-picker日期框,点击出现闪退问题处理
前端·vue.js·elementui
技术钱6 分钟前
element plus 多个form校验
前端
yume_sibai16 分钟前
HTML HTML基础(3)
前端·html
雨中散步撒哈拉22 分钟前
13、做中学 | 初一下期 Golang数组与切片
开发语言·后端·golang
米花丶23 分钟前
JSBridge安全通信:iOS/Android桥对象差异与最佳实践
前端·webview
0wioiw023 分钟前
Go基础(③Cobra)
开发语言·后端·golang
楼田莉子31 分钟前
C++算法专题学习:栈相关的算法
开发语言·c++·算法·leetcode
晨非辰36 分钟前
#C语言——刷题攻略:牛客编程入门训练(九):攻克 分支控制(三)、循环控制(一),轻松拿捏!
c语言·开发语言·经验分享·学习方法·visual studio
_oP_i43 分钟前
Java 服务接口中解决跨域(CORS,Cross-Origin Resource Sharing)问题
java·开发语言
陈序猿(代码自用版)1 小时前
【考研C语言编程题】数组元素批量插入实现(含图示+三部曲拆解)
c语言·开发语言·考研