JavaScript异步编程——10-async异步函数【万字长文,感谢支持】

news2024/12/23 6:54:21

异步函数(用 async 声明的函数)

异步函数的定义

使用async关键字声明的函数,称之为异步函数。在普通函数前面加上 async 关键字,就成了异步函数。语法举例:

 // 写法1:函数声明的写法
 async function foo1() {
 }
 ​
 // 写法2:表达式写法(ES5语法)
 const foo2 = async function () {
 }
 ​
 // 写法3:表达式写法(ES6箭头函数语法)
 const foo3 = async () => {
 }
 ​
 // 写法4:定义一个类,在类中添加一个异步函数
 class Person {
   async foo4() {
   }
 }

JS中的“异步函数”是一个专有名词,特指用async关键字声明的函数,其他函数则称之为普通函数。如果你在一个普通函数中定义了一个异步任务,那并不叫异步函数,而是叫包含异步任务的普通函数。

async (异步的)这个单词是 asynchronous 的缩写;相反,sync(同步的)这单词是 synchronous 的缩写。

上面的异步函数代码,执行顺序与普通函数相同,默认情况下会同步执行。如果想要发挥异步执行的作用,则需要配合 await 关键字使用。稍后我们再讲 async/await的语法。

异步函数的返回值

异步函数的返回值和普通函数差别比较大,需要特别关注。

普通函数的返回值,默认是 undefined;也可以手动 return 一个返回值,那就以手动 return的值为准。

异步函数的返回值永远是 Promise 对象。至于这个 Promise 后续会进入什么状态,那就要看情况了。主要有以下几种情况:

  • 情况1:如果异步函数内部返回的是普通值(包括 return undefined时)或者普通对象,那么Promise 的状态为fulfilled。这个值会作为then()回调的参数。

  • 情况2:如果异步函数内部返回的是另外一个新的 Promise,那么 Promise 的状态将交给新的 Promise 决定

  • 情况3:如果异步函数内部返回的是一个对象,并且这个对象里有实现then()方法(这种对象称为 thenable 对象),那就会执行该then()方法,并且根据then()方法的结果来决定Promise的状态

另外还有一种特殊情况:

  • 情况4:如果异步函数内部在执行时遇到异常或者手动抛出异常时,那么, Promise 处于rejected 状态。

上面这四种情况似曾相识,我们在前面学习“resolve() 传入的参数”、“then()方法的返回值”知识点时,都有类似的情况,知识都是相通的。

默认返回值

代码举例:

 async function foo2() {
   // 相当于 return undefined,也相当于 return Promise.resolve(undefined)
 };
 ​
 async function foo3() {
   Promise.resolve('qianguyihao');
   // 相当于 return undefined,也相当于 return Promise.resolve(undefined)
 };
 ​
 // foo2()、foo3()都是一个Promise对象
 foo2().then(res => {
   console.log(res); // 打印结果:undefined
 })
 ​
 foo3().then(res => {
   console.log(res); // 打印结果:undefined
 })

代码解释:异步函数即便没有手动写返回值,也相当于 return Promise.resolve(undefined)

返回普通值

比如下面这段代码:

 async function foo() {
   return 'qianguyihao'
 };

image-20230608114346235

可以看到,foo() 的返回值是Promise对象,不是字符串。上面的代码等价于下面这段代码:

 async function foo() {
   return Promise.resolve('qianguyihao');
 };

进而,我们可以通过 Promise 对象的then()方法。代码举例如下。

举例1:(异步函数中手动 return 一个值)

 async function foo() {
   return 'qianguyihao';
   // 上面这行代码相当于:return Promise.resolve('qianguyihao');
 };
 ​
 // foo() 是一个Promise对象
 foo().then(res => {
   console.log(res); // 打印结果:qianguyihao
 })

async/await 的使用

异步函数配合 await 关键字使用

我们可以在async声明的异步函数中,使用 await关键字来暂停函数的执行,等待一个异步操作完成。温馨提示:await 关键字不能在普通函数中使用,只能在异步函数中使用。

在等待异步操作期间,异步函数会暂停执行,并让出线程,使其他代码可以继续执行。一旦异步操作完成,该异步函数会恢复执行,并返回一个 Promise 对象。具体解释如下:

(1)await的后面是一个表达式,这个表达式要求是一个 Promise 对象(通常是一个封装了异步任务的Promise对象)。await执行完成后可以得到异步结果。

(2)await 会等到当前 Promise 的状态变为 fulfilled之后,才继续往下执行异步函数。

  • async 的返回值是 Promise 对象。

本质是语法糖

async/await 是在 ES8(即ES 2017)中引入的新语法,是另外一种异步编程解决方案。

async/await 本质是 生成器 Generator 的语法糖,是对Generator的封装。什么是语法糖呢?语法糖就是让语法变得更加简洁、更加舒服,有一种甜甜的感觉。

async/await 的写法使得编写异步代码更加直观和易于管理,避免了使用回调函数或Promise链的复杂性。认识到这一点,非常重要。

Promise、Generator、async/await的对比

我们在使用 Promise、async/await、Generator 的时候,返回的都是 Promise 的实例。

如果直接使用 Promise,则需要通过 then 来进行链式调用;如果使用 async/await、Generator,写起来更像同步的代码。

接下来,我们看看 async/await 的代码是怎么写的。

async/await 的基本用法

async 后面可以跟一个 Promise 实例对象。代码举例如下:

 const request1 = function () {
   const promise = new Promise((resolve, reject) => {
     requestAjax('https://www.baidu.com/xxx_url', (res) => {
       if (res.retCode == 200) {
         // 这里的 res 是接口1的返回结果
         resolve('request1 success' + res);
       } else {
         reject('接口请求失败');
       }
     });
   });
 ​
   return promise;
 };
 ​
 async function requestData() {
   // 关键代码
   const res = await request1();
   return res;
 }
 requestData().then(res => {
   console.log(res);
 });

用 async/await 封装Promise链式调用【重要】

假设现在有三个网络请求,请求2必须依赖请求1的结果,请求3必须依赖请求2的结果,如果按照ES5的写法,会有三层回调,会陷入“回调地狱”。

这种场景其实就是接口的多层嵌套调用。之前学过 Promise,它可以把原本的多层嵌套调用改进为链式调用

而本文要学习的 async/await ,可以把原本的“多层嵌套调用”改成类似于同步的写法,非常优雅。

代码举例:

 // 【公共方法层】封装 ajax 请求的伪代码。传入请求地址、请求参数,以及回调函数 success 和 fail。
 function requestAjax(url, params, success, fail) {
   var xhr = new xhrRequest();
   // 设置请求方法、请求地址。请求地址的格式一般是:'https://api.example.com/data?' + 'key1=value1&key2=value2'
   xhr.open('GET', url);
   // 设置请求头(如果需要)
   xhr.setRequestHeader('Content-Type', 'application/json');
   xhr.send();
   xhr.onreadystatechange = function () {
     if (xhr.readyState === 4 && xhr.status === 200) {
       success && success(xhr.responseText);
     } else {
       fail && fail(new Error('接口请求失败'));
     }
   };
 }
 ​
 // 【model层】将接口请求封装为 Promise
 function requestData1(params_1) {
   return new Promise((resolve, reject) => {
     requestAjax('https://api.qianguyihao.com/url_1', params_1, res => {
       // 这里的 res 是接口返回的数据。返回码 retCode 为 0 代表接口请求成功。
       if (res.retCode == 0) {
         // 接口请求成功时调用
         resolve('request success' + res);
       } else {
         // 接口请求异常时调用
         reject({ retCode: -1, msg: 'network error' });
       }
     });
   });
 }
 ​
 ​
 // requestData2、requestData3的写法与 requestData1类似。他们的请求地址、请求参数、接口返回结果不同,所以需要挨个单独封装 Promise。
 function requestData2(params_2) {
   return new Promise((resolve, reject) => {
     requestAjax('https://api.qianguyihao.com/url_2', params_2, res => {
       if (res.retCode == 0) {
         resolve('request success' + res);
       } else {
         reject({ retCode: -1, msg: 'network error' });
       }
     });
   });
 }
 ​
 function requestData3(params_3) {
   return new Promise((resolve, reject) => {
     requestAjax('https://api.qianguyihao.com/url_3', params_3, res => {
       if (res.retCode == 0) {
         resolve('request success' + res);
       } else {
         reject({ retCode: -1, msg: 'network error' });
       }
     });
   });
 }
 ​
 // 封装:用 async ... await 调用 Promise 链式请求
 async function getData() {
   // 【关键代码】
   const res1 = await requestData1(params_1);
   const res2 = await requestData2(res1);
   const res3 = await requestData3(res2);
 }
 ​
 getData();

上面这段代码比较长,我们在上一章学习《Promise的链式调用》时,已经详细讲过了。

await 后面也可以跟一个异步函数

前面讲到,await后面通常是一个执行异步任务的Promise对象。由于异步函数的返回值本身就是一个Promise,所以,我们也可以在await 后面也可以跟一个异步函数。

代码举例:

 const request1 = function () {
   return new Promise((resolve, reject) => {
     resolve('request1 请求成功');
   });
 };
 ​
 async function request2() {
   const res = await request1();
   return res;
 }
 ​
 async function request3() {
   // 【关键代码】request2() 既是一个异步函数,同样也是一个 Promise,所以可以跟在 await 的后面
   const res = await request2();
   console.log('res:', res);
 }
 ​
 request3();

异步函数的异常处理

前面讲过,如果异步函数内部在执行时遇到异常或者手动抛出异常时,那么, 这个异步函数返回的Promise 处于rejected 状态。

捕获并处理异步函数的异常时,有两种方式:

  • 方式1:通过 Promise的catch()方法捕获异常。

  • 方式2:通过 try catch捕获异常。

在处理异步函数的异常情况时,方式2更为常见。

如果我们不捕获异常,则会往上层层传递,最终传递给浏览器,浏览器会在控制台报错。

方式1:过 Promise的catch()方法捕获异常

function requestData1() {
  return new Promise((resolve, reject) => {
    reject('任务1失败');
  })
}

function requestData2() {
  return new Promise((resolve, reject) => {
    resolve('任务2成功');
  })
}

async function getData() {
  // requestData1 在执行时,遇到异常
  await requestData1();
  /*
  由于上面的代码在执行是遇到异常,所以,这里虽然什么都没写,底层默认写了如下代码:
  return Promise.reject('任务1失败');
  */

  // 下面这行代码不会再走了
  await requestData2();
}

// getData() 这个异步函数的返回值是一个 Promise,状态为 rejected,所以会走到 catch()
getData().then(res => {
  console.log(res);
}).catch(err => {
  console.log('err:', err);
});

打印结果:

err: 任务1失败

方式2:通过 try catch 捕获异常

如果你觉得上面的写法比较麻烦,还可以通过 try catch 捕获异常。

代码举例:

function requestData1() {
  return new Promise((resolve, reject) => {
    reject('任务1失败');
  })
}

function requestData2() {
  return new Promise((resolve, reject) => {
    resolve('任务2成功');
  })
}

async function getData() {
  try {
    // requestData1 在执行时,遇到异常
    await requestData1();
    /*
    由于上面的代码在执行是遇到异常,当前任务立即终止,所以,这里虽然什么都没写,底层默认写了如下代码:
    return Promise.reject('任务1失败');
    */

    // 下面这两代码不会再走了
    console.log('qianguyihao1');
    await requestData2();
  }
  catch (err) {
    // 捕获异常代码
    console.log('err:', err);
  }
}

getData();
console.log('qianguyihao2');

打印结果:

qianguyihao2
err1: 任务1失败

总结

在 async 函数中,不是所有的 异步任务都需要 await。如果两个任务在业务上没有依赖关系,则不需要 await;也就是说,可以并发执行,不需要线性执行,避免无用的等待。

参考链接

  • js async await 终极异步解决方案

  • 理解 JavaScript 的 async/await

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

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

相关文章

杨校老师项目之基于单片机STC89C52的智能环境监测系统【嵌入式】

获取全套资料: 有偿获取:mryang511688 技术:C语言、单片机等 摘要: 此设计可分为三个主要部分。此中的温度和湿度的检测功能,通过操纵单总线型温湿度传感器DHT11以数字形式显示,实现了切确测得温湿度的功能…

Python | Leetcode Python题解之第89题格雷编码

题目&#xff1a; 题解&#xff1a; class Solution:def grayCode(self, n: int) -> List[int]:ans [0] * (1 << n)for i in range(1 << n):ans[i] (i >> 1) ^ ireturn ans

uniapp 实现下拉刷新 下滑更新

效果图 在app或者小程序中向下滑动 会出现刷新数据 ,而上拉到底 需要更新数据 功能实现 主要俩种方式 依赖生命周期 在page.json中开启 page.json "style" : {"navigationBarTitleText" : "小小练习","backgroundTextStyle": &qu…

Linux环境部署与命令技巧

Linux环境部署与命令技巧 安装Java 首先确保已经安装了Java。通过运行以下命令检查Java是否已安装&#xff1a; java -version # 查看Java版本如果未安装Java&#xff0c;可以使用以下命令安装OpenJDK&#xff1a; sudo yum install java-11-openjdk # 安装OpenJDK 11创建一个…

用Swagger(工具Knife4j )代替postman来进行构建、测试和调试 API

什么是Swagger&#xff1f; Swagger 是一个用于设计、构建和文档化 RESTful Web 服务的开源框架。它允许开发者设计 API&#xff0c;然后生成对应的 API 文档&#xff0c;提供给团队成员或者第三方开发者查阅。Swagger 的核心是 OpenAPI 规范&#xff08;之前称为 Swagger 规范…

来学习线程啦

线程的相关概念 程序 简单点说&#xff1a;程序就是我们写的代码&#xff1b;也可以理解为&#xff1a;为完成特定任务&#xff0c;用某种语言编写的一组指令的集合 进程 进程是指运行中的程序。 比如&#xff1a;我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系…

Centos 6.10 安装oracle10.2.0.1

由于阿里云机房要下架旧服务器&#xff0c;单位未购买整机迁移服务&#xff0c;且业务较老不兼容Oracle11g&#xff0c;所以新购买一台新服务器进行安装Oracle10.2.0.1 &#xff0c;后续再将数据迁移到新服务器上。 对外ip 内部ip 数据库版本 操作系统版本 实例名 源库 1…

【MATLAB】Enigma机加密原理与自实现

文章目录 什么是EnigmaEnigma机加密通信流程Enigma的物理构造Enigma的加密设置Enigma加密通信密码重新设置Enigma加密消息拼接注意 Enigma的解密分解设置Enigma解密通信密码重新设置Enigma解密消息 Enigma的弱点MATLAB自实现Enigma加密与解密Enigma_functionRotate_functiontes…

排序(一)----冒泡排序,插入排序

前言 今天讲一些简单的排序,冒泡排序和插入排序,但是这两个排序时间复杂度较大,只是起到一定的学习作用,只需要了解并会使用就行,本文章是以升序为例子来介绍的 一冒泡排序 思路 冒泡排序是一种简单的排序算法&#xff0c;它重复地遍历要排序的序列&#xff0c;每次比较相邻…

K8S内容

K8S介绍 1、故障迁移:当某一个node节点关机或挂掉后&#xff0c;node节点上的服务会自动转移到另一个node节点上&#xff0c;这个过程所有服务不中断。这是docker或普通云主机是不能做到的 2、资源调度:当node节点上的cpu、内存不够用的时候&#xff0c;可以扩充node节点&…

详细分清Session,Cookie和Token之间的区别,以及JWT是什么东西

Cookie Cookie是一种小型的文本文件&#xff0c;由网站在用户访问时存储在其计算机或移动设备上&#xff0c;Cookie主要用于跟踪、识别和存储有关用户的信息。 简单来说Cookie就是用来存储某些后端发送给前端的数据&#xff0c;例如我们登陆后&#xff0c;后端会返回一个登录…

求正方形阴影部分面积

正方形边长6&#xff0c;求阴影部分面积 xy6① vw6② 1/26v1/23x1/263③ 1/26v1/26y1/266④ ③是左下角三角形的面积&#xff0c;④是左上角三角形的面积。 求解方程组得到x2 阴影部分面积1/2*3x3.

海豚调度器如何看工作流是在哪个worker节点执行

用海豚调度器&#xff0c;执行一个工作流时&#xff0c;有时成功&#xff0c;有时失败&#xff0c;怀疑跟worker节点环境配置不一样有关。要怎样看是在哪个worker节点执行&#xff0c;在 海豚调度器 Web UI 中&#xff0c;您可以查看任务实例&#xff0c;里面有一列显示host&a…

网络安全快速入门(九)MySQL进阶操作

上一章我们了解了对表及库的基本增删查改操作&#xff0c;本章我们针对增删查改内容进行与一些拓展&#xff0c; 9.1字段修饰及数据类型 我们之前在创建表时用到的格式为&#xff1a; create table 表名 ( 字段名1 字段数据类型&#xff08;数据类型长度&#xff09;, 字段名2 …

刷代码随想录有感(65):回溯算法——组合问题

题干&#xff1a; 代码&#xff1a; class Solution { public:vector<vector<int>> res;vector<int> tmp;void backtracking(int n, int k, int start){if(tmp.size() k){res.push_back(tmp);return;}for(int i start; i < n; i){tmp.push_back(i);bac…

[Algorithm][回溯][组合][目标和][组合总和]详细讲解

目录 1.组合1.题目链接2.算法原理详解3.代码实现 2.目标和1.题目链接2.算法原理详解3.代码实现 3.组合总和1.题目链接2.算法原理详解3.代码实现 1.组合 1.题目链接 组合 2.算法原理详解 思路&#xff1a;每次都只选一个数&#xff0c;此后只能选它后面的数函数设计&#xff…

全面提升数据采集效率:亮数据产品的应用与评估详解

全面提升数据采集效率&#xff1a;亮数据产品的应用与评估详解 文章目录 全面提升数据采集效率&#xff1a;亮数据产品的应用与评估详解背景应用场景&#xff1a;平台首页信息抓取准备评测素材详细的产品使用和评测流程产品介绍亮数据的IP代理服务亮数据的爬虫工具及采集技术 注…

elasticsearch使用Ngram实现任意位数手机号搜索

文章目录 Ngram自定义分词案例实战问题拆解 Ngram分词器定义Ngram分词定义Ngram分词示例Ngram分词应用场景 Ngram分词实战 Ngram自定义分词案例 当对keyword类型的字段进行高亮查询时&#xff0c;若值为123asd456&#xff0c;查询sd4&#xff0c;则高亮结果是&#xff1c;em&a…

项目管理-案例重点知识(整合管理)

项目管理&#xff1a;每天进步一点点~ 活到老&#xff0c;学到老 ヾ(◍∇◍)&#xff89;&#xff9e; 何时学习都不晚&#xff0c;加油 一、整合管理 案例重点 重点内容&#xff1a; &#xff08;1&#xff09;项目章程内容和作用 &#xff08;2&#xff09;项目管理计划…

乡村振兴的农业科技创新:加大农业科技投入,推广农业科技成果,提升农业科技创新水平,推动美丽乡村农业现代化

一、引言 随着全球化和信息化时代的到来&#xff0c;农业作为国民经济的基础&#xff0c;其现代化进程日益受到关注。在乡村振兴战略的大背景下&#xff0c;农业科技创新成为推动乡村经济转型升级、实现农业现代化的关键力量。本文旨在探讨如何通过加大农业科技投入、推广农业…