async和await随谈

news2024/11/18 10:31:34

以下只是个人观点,如果有出入或者错误之处,欢迎提出,你不需要纠结我的说法是否有一些小瑕疵,把其中你认为对的地方融入到自己的知识里去就好了,你学到的才是自己的。

在说async和await之前,先讲另一个东西迭代器。

迭代器是什么?

在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。更具体地说,迭代器是通过使用 next() 方法实现 Iterator protocol 的任何一个对象,该方法返回具有两个属性的对象: value,这是序列中的 next 值;和 done ,如果已经迭代到序列中的最后一个值,则它为 true 。如果 value 和 done 一起存在,则它是迭代器的返回值。

一旦创建,迭代器对象可以通过重复调用 next()显式地迭代。迭代一个迭代器被称为消耗了这个迭代器,因为它通常只能执行一次。在产生终止值之后,对 next()的额外调用应该继续返回{done:true}。

以上是一个mdn官方的解释。

下面我用代码具体演示下什么叫迭代器:

// 创建一个迭代器函数,传入一个数组,自动创建一个迭代器
function createIterator(array) {
    // 这里我是通过数组的下标获取数组的值,方式无所谓,你也可以换成其他实现方式
    let index = 0
    return {
        next() {
            if (index < array.length) {
                return { done: false, value: array[index++] }
            }
            return { done: true }
        }
    }
}


const nums = [1, 3, 5, 6]

const numsIterator = createIterator(nums)

console.log(numsIterator.next())
console.log(numsIterator.next())
console.log(numsIterator.next())
console.log(numsIterator.next())
console.log(numsIterator.next())
console.log(numsIterator.next())

// { done: false, value: 1 }
// { done: false, value: 3 }
// { done: false, value: 5 }
// { done: false, value: 6 }
// { done: true, value: undefined }
// { done: true, value: undefined }

概括下,什么叫迭代器?

1. 迭代器是一个对象

2. 该对象包含next函数,且next函数返回一个对象,该对象包含{ done: boolean, value: any }字段,且当值结束时,done为true

在这里提一下,es6的for of 方法

for of 可以遍历可迭代对象,非可迭代对象,for of 是不可以遍历的

for of 内部实现实际上就是调用可迭代对象的next方法,并返回其value值。

举个例子:for of 遍历 Array

const nums = [1, 2, 4, 7]

for(const num of nums) {
    console.log(num)
}

console.log(nums[Symbol.iterator]); // [Function: values]
console.log(nums[Symbol.iterator]()); // Object [Array Iterator] {}
console.log(nums[Symbol.iterator]().next()); // { value: 1, done: false }
console.log(nums[Symbol.iterator]().next().value); // 1

可以从上面的代码上看出来,我们定义的数组里存在一个名字叫 Symbol.iterator 的函数,实际上这就是官方的规范,可迭代对象需要实现这个方法。

什么叫可迭代对象?

mdn的描述:

若一个对象拥有迭代行为,比如在 for...of 中会循环哪些值,那么那个对象便是一个可迭代对象。一些内置类型,如 Array 或 Map 拥有默认的迭代行为,而其他类型(比如Object)则没有。

为了实现可迭代,一个对象必须实现 @@iterator 方法,这意味着这个对象(或其原型链中的任意一个对象)必须具有一个带 Symbol.iterator 键(key)的属性。

简单的说,就是一个对象中必须包含Symbol.iterator方法,且该方法返回一个迭代器,它就是一个可迭代对象。

其实你了解过原型链的话,你就会知道,该方法是在对象本身,还是原型链的对象上,实际上都会被调用的。

常见的可迭代对象:Array、string、Map、Set、NodeList等

举个简单的例子,实现一个可迭代对象:

const iterableObj = {
  // 这里我是叫nums,你也可以起一个其他名字,这个不是重要的,重要的是[Symbol.iterator]方法的实现
  nums: [1, 2, 4, 7],
  // 为什么使用[]把 Symbol.iterator 括起来,这个是对象中动态key的写法,大家可以去看下官方文档
  [Symbol.iterator]: function() {
    let index = 0
    return {
      // 这里为什么使用箭头函数呢
      next: () => {
        if (index < this.nums.length) {
          return { done: false, value: this.nums[index++] }
        }
        return { done: true }
      }
    }
  }
}

// for of 遍历,可以看到打印信息,是可以遍历的,且正确输出了值
for(const item of iterableObj) {
  console.log(item);
}
// 1, 2, 4, 7

上面的代码里next方法为什么使用箭头函数呢?

大家可以看下,[Symbol.iterator]方法,返回的是一个对象,该对象中有一个next方法,如果我们不使用箭头函数,那它的this指向就不能保证一直是指向我们的当前可迭代对象。

至于为什么使用箭头函数就会始终指向我们的nums,这个就是箭头函数的特殊性了,箭头函数中是不会绑定this的,所以它就会沿着作用域向上查找,它的上层作用域,可以看出来就是[Symbol.iterator]函数,不知道为啥的,建议去看下this指向问题,这里不详细讲述了。

生成器是什么?

生成器函数提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数,同时它可以自动维护自己的状态。生成器函数使用 function*语法编写。最初调用时,生成器函数不执行任何代码,而是返回一种称为 Generator 的迭代器。通过调用生成器的下一个方法消耗值时,Generator 函数将执行,直到遇到 yield 关键字。

可以根据需要多次调用该函数,并且每次都返回一个新的 Generator,但每个 Generator 只能迭代一次。

举个简单的例子:

// 可以通过打印信息查看生成器的执行流程
function* createIterator() {
  console.log('----这是第一段代码----')
  yield
  console.log('----这是第二段代码----')
}

const foo = createIterator()

foo.next() // 执行第一段代码
foo.next() // 执行第二段代码

从上面的代码可以看出,生成器的标志是function后面加*,并且结合着yield使用,且是以yield为分割执行代码块。

function* createIterator() {
  console.log('----开始执行了----')
  yield
  console.log('----执行结束了----')
  return '1111'
}

const foo = createIterator()

console.log('第一段代码的值:', foo.next());
console.log('第二段代码的值:', foo.next());

// ----开始执行了----
// 第一段代码的值: { value: undefined, done: false }
// ----执行结束了----
// 第二段代码的值: { value: '1111', done: true }

从上面代码的打印信息来看,是不是有点熟悉,是不是和迭代器的返回值很像,本质上生成器就是一种特殊的迭代器。

function* createIterator() {
  console.log('----第一段代码----')
  const value1 = 100
  console.log('第一段代码的值:', value1);
  const n = yield value1
  console.log('----第二段代码----')
  const value2 = 200 * n
  console.log('第二段代码的值:', value2);
  yield value2
  console.log('----第三段代码----')
}

const foo = createIterator()

console.log('第一段代码:', foo.next());
console.log('第二段代码:', foo.next(10));
console.log('第三段代码:', foo.next());

从上面代码可以看到生成器是可以传参的,我们通过next方法传入的10,在生成器里通过 yield 的返回值获取到了,看到这估计有人疑惑了,其实这就是生成器的规范,以方便做参数的传递,那估计又会有人有疑问了,那我第一个yield之前的代码如果想获取参数怎么办,其实这是陷入一个思维误区了,第一个yield之前的代码段传参,不需要通过next方法传参,直接在createIterator函数中传参即可。

终止执行生成器的方法:

function* createIterator() {
  console.log('----第一段代码----')
  const value1 = 100
  console.log('第一段代码的值:', value1);
  const n = yield value1
  // 下面的foo.return相当于在此执行了 return n
  console.log('----第二段代码----')
  const value2 = 200 * n
  console.log('第二段代码的值:', value2);
  yield value2
  console.log('----第三段代码----')
}

const foo = createIterator()

console.log('第一段代码:', foo.next());
console.log('第二段代码:', foo.return(10)); // return方法
console.log('第三段代码:', foo.next());

// ----第一段代码----
// 第一段代码的值: 100
// 第一段代码: { value: 100, done: false }
// 第二段代码: { value: 10, done: true }
// 第三段代码: { value: undefined, done: true }

可以通过生成器的内置方法return终止执行,它的原理很简单,就是在你执行代码块中插入一个return语句。

例如上面的代码我在第二个代码块调用了return,则相当于在第一个yield的后面直接return数据,通过打印的值也可以看出来。

还有一种就是抛出错误了,这个就不多说了,通过throw的方式抛出错误即可。

我们来看看async和await解决了一个什么问题?

正常情况下,我们实现一个请求的回调嵌套请求,在不使用async 的情况下怎么实现。

function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(url)
    }, 2000)
  })
}

requestData('hello').then(res => {
  console.log('当前的返回值1:', res);
  // 现在我们需要这个返回值作为参数,继续请求
  requestData(res + ' world').then(res1 => {
    console.log('当前的返回值2:', res1);
  })
})

这种方式如果嵌套很多,可读性和维护性就很差,有可能到以后你再回来看你自己的代码,自己都看不懂。

当然在很早的时候也有人做一些非常简单的优化,例如:

function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(url)
    }, 2000)
  })
}

requestData('hello').then(res => {
  console.log('当前的返回值1:', res);
  // 现在我们需要这个返回值作为参数,继续请求
  return requestData(res + ' world')
}).then(res1 => {
  console.log('当前的返回值2:', res1);
})

虽然比之前的好一点,那本质问题还在,并没什么区别。

在之后在es6之前,也有社区有比较好的实现,使用promise+生成器实现,例如:

function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(url)
    }, 2000)
  })
}

function* getData() {
  const res1 = yield requestData('hello')
  const res2 = yield requestData(res1 + ' world')
  console.log(res2);
}

// 这个函数,也是有一个第三方库的,叫co,可以搜一下
function excuGenerator(genFn) {
  const generator = genFn()
  function excu(res) {
    const generatorValue = generator.next(res)
    if (generatorValue.done) {
      return generatorValue.value
    }
    generatorValue.value.then(res => {
      excu(res)
    })
  }
  excu()
}

excuGenerator(getData)

看到这里是不是感觉有点像,和async和await有点像,实际上async和await就是一个语法糖,它的实现逻辑和这个很像。

async 和 await

async你可以理解为异步,但不代表async函数就是一个异步函数,举例:

async function foo() {
  console.log('我是在async中执行的');
}

foo()

console.log('我是在async后执行的');

// 我是在async中执行的
// 我是在async后执行的

看打印的数据顺序,能看出来什么,其实他还是按执行顺序打印的,这是因为虽然你在函数前面加了async 关键字,但是它执行顺序还是和普通函数一样,你就把它当成普通函数执行就好。

 我们使用async和await重写之前的代码:

function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(url)
    }, 2000)
  })
}

async function getData() {
  const res1 = await requestData('hello')
  const res2 = await requestData(res1 + ' world')
  console.log(res2);
}

getData()

 异步函数和普通函数的区别:

1. 异步函数的返回值是一个promise,例如:

async function foo() {
}

const res = foo()

console.log(res);

// Promise { undefined }

2. 异步函数的异常会被catch捕获,如果捕获,不会影响后续的代码执行

async function foo() {
  throw new Error('error message')
}

foo().catch((res) => {
  console.log('error111: ', res);
})

console.log('后续代码');

3. 异步函数可以使用 await 关键字

function requestData(url) {
  // 1. 第一种方式
  // return new Promise((resolve, reject) => {
  //   setTimeout(() => {
  //     resolve(url)
  //   }, 2000)
  // })
  // 2. 第二种方式
  // return {
  //   then(resolve, reject) {
  //     setTimeout(() => {
  //       resolve(url)
  //     }, 2000)
  //   }
  // }
  // 3. 第三种方式
  return url
}

async function getData() {
  const res1 = await requestData('hello')
  const res2 = await requestData(res1 + ' world')
  console.log(res2);
}

getData()

到这基本就结束了,虽然内容不是很深奥,都是些比较基础的知识,但也希望对你有所帮助,也算是我的一个记录,毕竟输出才是检验知识的标准,不喜勿喷,如果有错误,欢迎指正和交流。

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

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

相关文章

ADI Blackfin DSP处理器-BF533的开发详解56:DSP控制ADV7180采集图像到LCD显示(含源码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 代码实现功能 代码实现了采集一帧 720625 尺寸的 P 制 CVBS 信号源&#xff0c;以 YUYV422 的数据格式保存&#xff0c;通过 MDMA 将奇偶场数据交…

比较级和最高级

1. 单音节词和少数双音节词的比较级和最高级的构成 1)常见的单音节词和少数以-er和-ow结尾的双音节词&#xff0c;比较级在末尾加-er,最高级在末尾加-est 例如&#xff1a;单音节词&#xff1a;small-smaller-smallest, tall-taller-tallest; 双音节词&#xff1a;clever-cl…

第二十九章 linux-i2c子系统二

第二十九章 linux-i2c子系统二 文章目录第二十九章 linux-i2c子系统二linux-i2c数据结构分析linux-i2c驱动框架IIC核心IIC总线驱动IIC设备驱动linux-i2c设备实现硬件拓扑linux-i2c数据结构分析 driver 设备驱动模型 i2c_client来描述一个挂载在I2C总线上的I2C设备。 struc…

【图像处理】数字图像处理笔记

文章目录直方图处理滤波器图像复原形态学图像处理灰度形态学——多使用平坦结构元&#xff08;SE&#xff09;【数字图像处理P428图像分割1、canny边缘检测【数字图像处理P463】图像的表征特征描述子目标检测分类器AdaBoost算法——集成学习、迭代算法直方图处理 1、直方图均衡…

ARM处理器概论

目录 一、ARM处理器概述 1.ARM的含义 2.ARM公司 3.主流的两种处理器 RISC处理器&#xff08;精简指令集&#xff09; CISC处理器&#xff08;复杂指令集&#xff09; 4.SOC 二、ARM指令集概述 1.指令与指令集 指令 指令集 2.ARM指令集 ARM指令集 Thumb指令集 3.编…

vuex----的辅助函数mapState, mapActions, mapMutations用法和混入

Vuex的辅助函数mapState, mapActions, mapMutations用法和混入 爱学习的渣渣关注IP属地: 江苏 2022.03.28 00:14:13字数 287阅读 469 一.使用mapState来获取state里的值 第一步先解构 1.最简单的使用数组的形式来获取模块中state中的值 2.用对象的形式来获取模块中的state的…

面试官:Spring refresh过程是怎样的?

小熊学Java网站&#xff1a;https://javaxiaobear.gitee.io/&#xff0c;每周持续更新干货&#xff0c;建议收藏&#xff01; 1. Spring refresh 流程 refresh 是 AbstractApplicationContext 中的核心方法&#xff0c;负责初始化 ApplicationContext 容器&#xff0c;容器必须…

【算法】七月算法打卡

# 2022-07-11 深拷贝 const copyObject (obj {}) > {let newObj nullif (typeof (obj) object && obj ! null) {newObj obj instanceof Array ? [] : {}// 进入下一层进行递归for (let i in obj) newObj[i] copyObject(obj[i])} else {newObj obj}return …

手机(Android)刷NetHunter安装指南,无需ssh执行kali命令, NetHunter支持的无线网卡列表!

一、安装NetHunter 前提&#xff1a;确保手机已经root&#xff0c;已装上magisk。如果没有root&#xff0c;可用尝试magisk root 后执行此文 1、下载Nethunter&#xff1a;Get Kali | Kali Linux 然后push 到sdcard 里&#xff0c; 2、打开magisk&#xff0c;选择刚刚下好的…

krpt.dll丢失怎么安装?怎么修复快一点

krpt.dll丢失怎么安装&#xff1f;其实你只要记住一点&#xff0c;只要是dll文件丢失&#xff0c;那么就不外乎那几种修复方法&#xff0c;自己下载丢失的文件安装&#xff0c;或者使用dll修复工具&#xff0c;这些方法都是可以修复的。下面我们详细的说一说。 目录 一.krpt.…

性能优化-内存泄漏、内存溢出、cpu占用高、死锁、栈溢出、FullGC频繁检测手段-总结与分享

介绍 什么是内存泄漏 含义&#xff1a;内层泄露是程序中己动态分配的堆内存由于某种原因程序未释放或无法释放&#xff0c;造成系统内存的浪费。&#xff08;换言之&#xff0c;GC回收不了这些不再被使用的对象&#xff0c;这些对象的生命周期太长&#xff09; 危害&#xff…

Linux学习03-Linux基础命令1

1 开始执行命令 command [-options] parameter1 parameter2 一行命令中第一个输入的部分绝对是命令或者可执行文件。 例如 ls命令列出目录的文件&#xff0c;需要列出隐藏文件则加 -al。 ls -al ls ls -a -l 2 基础命令操作 显示日期与时间的命令&#xff1a;date 显示日…

Ubuntu22.04系统中二进制包安装公有云k8s

目录公有云版k8s的架构是怎样的公有云中创建k8s实例的过程如下二进制法创建k8s的一般过程Kubernetes的重要性check nodes每台服务器执行基线配置CA rootetcd HA cluster根据CA根证书创建etcd的专有CA证书将etcd注册成为systemd服务配置各master节点的etcd.confansible配置各个m…

东北大学2023分布式操作系统考试重点

Note&#xff1a;以下10个点为老师画的重点 1.分布式系统目标 第一章 分布式系统概论&#xff0c;第二节 2.RPC过程 第四章 分布式通信管理&#xff0c; 第二节 客户过程以普通方式调用相应的客户存根客户存根建立消息并激活内核陷阱内核将消息发送到远程内核远程内核将消息…

计算机毕设Python+Vue写字楼物业管理系统(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

C++ Reference: Standard C++ Library reference: Containers: map: map: operator[]

C官网参考链接&#xff1a;https://cplusplus.com/reference/map/map/operator[]/ 公有成员函数 <map> std::map::operator[] C98 mapped_type& operator[] (const key_type& k); C11 mapped_type& operator[] (const key_type& k); mapped_type& …

SQL基础——数据更新

数据更新前言思维导图数据的插入&#xff08;INSERT语句的使用方法&#xff09;什么是INSERTINSERT语句的基本语法语法4.1 INSERT语句代码示例4.2 创建 ProductIns表的CREATE TABLE语句代码示例4.3 向表中插入一行数据代码示例4.4 INSERT插入多行数据列清单的省略代码示例4.5 省…

chatGPT的49种应用场景,及各开发语言对接chatGPT参考指南

前沿 可能有人在问我&#xff0c;勇哥为什么chatGPT都被微信下架了&#xff0c;你还要写相关的chatGPT的文章呢&#xff1f;其实我们先不论微信下架的原因&#xff0c;单说chatGPT的达芬奇模型给勇哥带来的科技感早就超越了一切&#xff0c;所以勇哥依旧决定连续熬两个夜为大家…

【与达梦同行】达梦驱动图谱

达梦驱动图谱 摘要 达梦提供了大部分主流开发语言的驱动接口&#xff0c;在我用使用过的国产数据库中对客户端驱动的支持应该算是非常不错的。本文主要介绍达梦的驱动开发&#xff0c;通过实际操作&#xff0c;从环境搭建到实践验证&#xff0c;介绍了达梦各种语言驱动的详细使…

博途S7-1500T 使用工艺对象驱动液压轴(含SimaHydTO库)

利用工艺对象控制液压轴位置含PLC控制和仿真程序文档资料下载地址如下: https://support.industry.siemens.com/cs/document/109756217/simatic-s7-1500(t)-lsimahydto-for-hydraulic-applications-and-hydraulic-presses?dti=0&dl=en&lc=zh-CNhttps://support.indu…