【面试题】面试官:你能自己实现一个async await吗?

news2024/9/28 13:23:37

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天(点击查看活动详情)

相信大家对于Promise都不再陌生了,简易版的Promise对象源码我们也手撕过一次了,那接下来我们聊聊Promise的语法糖async-await,那让我们从官方的async开始,再到自己如何一步一步实现。

给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

前言

首先我们要知道以下几个相关的知识点:

  1. async 函数是AsyncFunction构造函数的实例, 并且其中允许使用await关键字。
  2. 当我们使用 async 声明了一个异步函数,那么我们就可以在这个异步函数内部使用 await 关键字。
  3. await 关键字只能接在async函数中使用,不能单独出现。
  4. async函数 返回的是一个状态为'fulfilled'Promise对象,如果有 return xxx;那就相当于resolve(xxx);如果没有return,则返回Promise {<fulfilled>: undefined}Promise对象。
  5. await关键字后面最好接的是Promise对象,以便于更好的实现异步(并不意味着不能接非Promise对象)。

官方的async await

function request(num) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * 10);
    }, 1000);
  });
}

async function fn() {
  const res1 = await request(1)
  const res2 = await request(res1)
  console.log(res2);
}

console.log(fn());

复制代码

将上一个await的结果作为第二个await的参数,最后将第二个await的结果打印出来。

其实明白Promise.then链式调用的伙伴就知道,这个效果使用.then的链式调用就可以实现。当然在.then里面拿到resolve出来的结果再给到下一个.then作为参数,以此往复就可以实现,但是在链式调用传参时,很容易把自己绕晕,而且我如果写了很多个await那就会嵌套很深,最后的代码会是一大坨,不美观。

那如果我不这样链式使用,我该如何等上一个Promise对象的执行结果出来之后,把结果作为第二个Promise对象的参数呢,这个时候我们就需要知道在ES6中提供了一种为异步编程解决方案的函数Generator函数,它是我们实现async await的主角之一,在这我不过多介绍,直接带大家迅速了解他它的用法,我们在实现async await就是使用了它的简单用法。

Generator函数

Generator 函数是一个状态机,封装了多个内部状态。 执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号(*);二是,函数体内部使用yield表达式,定义不同的内部状态。

function* gen() {
  yield 1;
  yield 2;
  yield 3;
  return 4;
}
const g = gen()
console.log(g);
复制代码

我们直接打印这个函数执行结果看看是什么样子?

诶?怎么就是一个编译好的gen函数呢,那个return 4呢? **Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。**

那如果我在调用next方法的时候传了一个参数,那这个参数会给到谁呢?

诶?为啥好像没啥变化呀?先别急,我换种写法你再看看

原来在next里面的参数给到了上一次yield的执行结果,并且在yield都执行完了之后内部的状态为true意味着,这个gen函数已经执行完了。

既然await 后面建议接Promise对象,那我就把yield 后面的换成Promise对象不就好了吗?这样我不就能控制在上一个Promise对象执行结束之后拿到了结果,再使用next传参不就是我们最开始想要的效果吗?

给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

只要这个Promise对象的状态变更为'fulfilled',那我就在它的.then里面传参不就好了

generatorToaAsync()

有以上这些基础之后我们来使用一个高阶函数来实现async await的功能,因为我们不能呢声明像await一样的关键字

function* gen() {
  
  ...
}

function generatorToaAsync(generatorFn){
    
    // ...
    return // 具有async函数功能的函数
}

const asyncFn = generatorToaAsync(gen);

console.log(asyncFn());  // Promise{}
复制代码

我们使用高阶函数声明一个函数,这个函数接受一个参数(generator函数),返回出一个具有async功能的函数

// 模拟async
function generatorToaAsync(generatorFn) {
  // ...
  //具有async功能的函数
  return function () {
    return new Promise((resolve, reject) => {

    })
  }
}
复制代码

这样万里长征的第一步,返回了一个具有async功能的Promise对象。

结合一下我们上面generator的部分例子

function foo(num) {
  // console.log(num);
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(num * 2);
    }, 1000);
  })
}

function* gen() {
  const num1 = yield foo(1);
  const num2 = yield foo(num1);
  const num3 = yield foo(num2);
  return num3;
}

// 模拟async
function generatorToaAsync(generatorFn) {
  // ...
  //具有async功能的函数
  return function () {
    const gen = generatorFn()  // 相当于前面加载好整个generator函数,,方面后面.next()去一层一层执行
    return new Promise((resolve, reject) => {
      const next1 = gen.next()
      next1.value.then(res1 => {
        const next2 = gen.next(res1)
        next2.value.then(res2 => {
          const next3 = gen.next(res2)
          next3.value.then(res3 => {
            resolve(gen.next(res3).value)
          })
        })
      })
    })
  }
}

const asyncFn = generatorToaAsync(gen);

// console.log(asyncFn());  // Promise{}

asyncFn().then(res => {
  console.log(res);
})
复制代码

我们借助了generator函数next方法,改变其gen函数内部yield的状态,最终我们将gen函数return 的值再通过我们返回的Promise对象 resolve出来,让我们看一下效果;

让我们看一眼官方async await实现的效果

是不是和我们实现的效果一致,最后我们只需要优化改进一下,就实现了async函数的效果;因为上面我们是站在上帝视角,知道gen函数里面有几个yield这样我们就是调用了三次.then就可以resolve出来。

首先我们考虑到this的指向问题,我们先将作为参数的(generator函数的this绑定到我们的定义的这个高阶函数generatorToaAsync身上),再将gen函数中执行的Promise函数全部作为参数给到generatorToaAsync

const gen = generatorFn.apply(this, arguments)
复制代码

既然不知道我们要调用几次.then,那么我们就是定义一个函数来判断Promise的状态,在函数内部使用递归调用

function loop(key,arg) {
        let res = null;
        res = gen[key](arg); // == gen.next(arg)  // { value: Promise { <pending> }, done: false }
        const { value, done } = res;
        if(done) {
          return resolve(value);
        }else {   // 没执行完yield
          // Promise.resolve(value) 为了保证value 中 Promise状态已经变更成'fulfilled'
          Promise.resolve(value).then(val => loop('next',val));
        }
      }

      loop('next')
复制代码

首先我们执行一个这个函数,将'next'用字符串作为参数,在函数内部使用变量来调用next方法,每一次循环拿到的都是 { value: Promise { <pending> }, done: false }根据done的状态判断是继续递归,还是resolve出结果在函数内部声明一个res 存放 每一次的状态和值。这样就实现了最终版的。

function foo(num) {
  // console.log(num);
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(num * 2);
    }, 1000);
  })
}

function* gen() {
  const num1 = yield foo(1);
  const num2 = yield foo(num1);
  const num3 = yield foo(num2);
  return num3;
}

// 模拟async
function generatorToaAsync(generatorFn) {
  // ...
  //具有async功能的函数
  return function () {

    const gen = generatorFn.apply(this, arguments)
    // const gen = generatorFn()

    return new Promise((resolve, reject) => {

      function loop(key,arg) {
        let res = null;

        res = gen[key](arg); // 等价于gen.next(arg)  // { value: Promise { <pending> }, done: false }

        const { value, done } = res;
        if(done) {
          return resolve(value);
        }else {   // 没执行完yield
          // Promise.resolve(value) 为了保证value 中 Promise状态已经变更成'fulfilled'
          Promise.resolve(value).then(val => loop('next',val));
        }
      }

      loop('next')

    })
  }
}

const asyncFn = generatorToaAsync(gen);

// console.log(asyncFn());  // Promise{}

asyncFn().then(res => {
  console.log(res);
复制代码

写在最后

虽然我们不能使用await关键字,但是我们可以使用generator函数里面的yield,然后将整个generator函数作为参数传递给我们定义的高阶函数,就能实现官方async await 一样的效果了。

给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/55810.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaWeb】第六章 xml

文章目录1、XML简介2、xml语法3、xml解析4、Dom4j类库的使用5、dom4j解析xml1、XML简介 xml是可扩展的标记性语言&#xff0c;xml的主要作用有&#xff1a; 用来保存数据&#xff0c;而且这些数据具有自我描述性 做为项目或者模块的配置文件做为网络传输数据的格式&#xff0…

QML 如何显示文本?Text可以有多少功能?

目录1.如何显示文本&#xff1f;2. Text有哪些主要功能&#xff1f;2.1 基本属性示例2.2 字重属性2.3 字体样式2.4 字体上标下标支持2.5 富文本2.6 文字换行 缩略1.如何显示文本&#xff1f; Text {font.pixelSize: 20; text: "这是20普通文字"} //一行即可以上代码…

12.2排序

目录 0.做题的失误 1.引用传值和传址 1.斐波那契数列 一.快速排序 1.挖坑法 2.优化 2.1 随机取数法 2.2 三数取中法 2.3把基准值相同的值移到基准旁边 2.4引用直接插入排序 3.Hoare 法: 4.非递归法 5.总结 二,归并排序 1.原理 2.代码实现 3.分析 4.非递归 5…

Git(第一篇)——Git的下载与安装(史上最全最详细)

Git&#xff08;第一篇&#xff09;——Git的下载与安装&#xff08;史上最全最详细&#xff09; 目录Git&#xff08;第一篇&#xff09;——Git的下载与安装&#xff08;史上最全最详细&#xff09;git的下载git的安装git的下载 如果你还没有下载Git&#xff0c;可直接到git…

什么是数据管理能力成熟度评估(DCMM)

GB/T 36073-2018 《数据管理能力成熟度评估模型》&#xff08;Data Management Capability Maturity Assessment Model&#xff0c;简称&#xff1a;DCMM&#xff09;是我国数据管理领域首个国家标准。该标准将组织对象的数据管理划分为八大能力域&#xff08;数据战略、数据治…

【Hbase】第一章——从原理剖析

文章目录1. HBase的实现原理1.1 HBase功能组件1.2 表和Region1.3 Region的定位2. HBase运行机制2.1 HBase系统架构2.2 Region服务器工作原理2.3 Store工作原理2.4 HLog工作原理3. HBase应用方案3.1 HBase实际应用中的性能优化方法3.2 HBase性能监视3.3 在HBase之上构建SQL引擎3…

【图像压缩】DCT图像无损压缩【含GUI Matlab源码 726期】

⛄一、DCT图像无损压缩简介 1 图像压缩 图像压缩按照压缩过程中是否有信息的损失以及解压后与原始图像是否有误差可以分为无损压缩和有损压缩两大类。无损压缩是指不损失图像质量的压缩&#xff0c;它是对文件的存储方式进行优化&#xff0c;采用某种算法表示重复的数据信息&a…

关于Jetpack Compose的初步使用、学习和总结

初步使用和学习ComposeJetpack Compose简要介绍创建一个Jetpack Compose项目自定义组合函数MessageCard通过修饰符&#xff0c;进一步改善布局为什么使用ComposeCompose 与 XML总结与期望Jetpack Compose 简要介绍 根据developers上的介绍&#xff0c;Jetpack Compose 是推荐…

【3D目标检测】Rethinking Pseudo-LiDAR Representation

目录概述细节证明基于伪点云的3D目标检测算法效果好的原因并不是伪点云这种数据表示基于深度图的图像表示的算法PatchNet证明基于伪点云的3D目标检测算法效果好的原因是从图像到点云坐标系转换的过程概述 本文是基于图像的3D目标检测算法。 贡献&#xff1a; 作者认为基于伪点…

开放式运动耳机排行榜,排行靠前的五款高性能耳机分享

智能产品的发展迅猛&#xff0c;作为生活必需品的耳机&#xff0c;更是在不断的更新&#xff0c;尤其是对于运动爱好者而言&#xff0c;以往的入耳式蓝牙耳机存在汗渍入耳等问题。而为了有效解决这一些列问题&#xff0c;新型的骨传导耳机随之诞生了&#xff0c;相比入耳式的蓝…

一定要用Photoshop?no!动手用Python做一个颜色提取器! ⛵

&#x1f4a1; 作者&#xff1a;韩信子ShowMeAI &#x1f4d8; Python3◉技能提升系列&#xff1a;https://www.showmeai.tech/tutorials/56 &#x1f4d8; 计算机视觉实战系列&#xff1a;https://www.showmeai.tech/tutorials/46 &#x1f4d8; 本文地址&#xff1a;https://…

直播 | 数据仓库?数据湖?停止纠结,流批融合的极速 Lakehouse来了!

万物皆数据的时代&#xff0c;各行各业对数据分析架构的要求日益拔高&#xff0c;打破传统的数据湖应需而生。企业得以用更低廉的成本、更完善的 ACID 支持、更实时的方式&#xff0c;导入并存储所有结构化、半结构化和非结构化数据。得益于数据湖良好的伸缩性和灵活性&#xf…

jQuery 安装

网页中添加 jQuery 可以通过多种方法在网页中添加 jQuery。 您可以使用以下方法&#xff1a; 从 jquery.com 下载 jQuery 库从 CDN 中载入 jQuery, 如从 Google 中加载 jQuery下载 jQuery 有两个版本的 jQuery 可供下载&#xff1a; Production version - 用于实际的网站中…

渲染时间过长?这些参数设置学起来

渲染时间 为了契合创作者的需求&#xff0c;V-Ray渲染器近年来迭代迅速&#xff0c;新版本的上线&#xff0c;便利了更多用户。但也有小伙伴在使用后反馈&#xff1a; 我的渲染器明明已经升级到最高版本了&#xff0c;为什么渲染时间还这么慢&#xff1f; 实际上&#xff0c;出…

如何通过一个项目征服Java

Java早已经不是高大山的稀世珍品了&#xff0c;程序员也不再是高科技工作者&#xff0c;而被称为码农 &#xff0c;为什么呢&#xff1f;因为Java后台的很多基础技术都已经固定了&#xff0c;也就是说主要你从头到尾学一遍就能会 &#xff0c;淘宝双十一搞不定&#xff0c;但是…

2022年12月深圳/珠海/佛山/东莞数据分析CPDA认证报名

2022年12月深圳/珠海/佛山/东莞数据分析CPDA认证报名 CPDA数据分析师认证是中国大数据领域有一定权威度的中高端人才认证&#xff0c;它不仅是中国较早大数据专业技术人才认证、更是中国大数据时代先行者&#xff0c;具有广泛的社会认知度和权威性。 无论是地方政府引进人才、…

HAL库(STM32CubeMX)之看门狗学习及实操(STM32F767IGTX)

系列文章目录 HAL库&#xff08;STM32CubeMX&#xff09;——ADC学习总结&#xff08;包含单次/连续模式下的轮询/中断/DMA&#xff09;&#xff08;蓝桥杯STM32G431RBT6&#xff09; HAL库(STM32CubeMX)——DAC学习&#xff08;STM32G431RBT6&#xff09; HAL库(STM32CubeM…

Innodb如何实现表--上篇

Innodb如何实现表--上篇数据是如何被管理起来的表空间段区页行行记录格式Compact记录行格式Redundant行记录格式行溢出数据Compressed和Dynamic行记录格式Char的行存储结构小结数据是如何被管理起来的 从InnoDB存储引擎的逻辑存储结构看&#xff0c;所有数据都被逻辑地存放在一…

[附源码]计算机毕业设计JAVA宿舍管理系统

[附源码]计算机毕业设计JAVA宿舍管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis M…

十万部冷知识:奥运会冠城市名,世界杯为什么冠国名?

不知道大家发现没有&#xff0c;凡是给奥运会、亚运会等很多比赛取名的时候&#xff0c;往往都是给它冠以城市的名字。比如&#xff0c;北京冬奥会、广州亚运会、北京奥运会等等&#xff0c;而称呼世界杯的时候&#xff0c;我们往往是冠以国家的名字称呼的&#xff0c;诸如&…