异步场景: promise、async函数与await命令介绍

news2024/12/23 14:12:17

如果你也对鸿蒙开发感兴趣,加入“Harmony自习室”吧!扫描下方名片,关注公众号,公众号更新更快,同时也有更多学习资料和技术讨论群。

在鸿蒙的开发中,我们时常会遇到promise异步场景,有同学反馈说希望提一下。

异步开发这部分的内容比较多,我不确定这位朋友具体想讨论是哪些方面,那我从两部分来讨论下,希望能提供一些帮助:

    1. 基本的开发角度,常用使用方法;

    2. 拿一个问题来讨论调用关系。

【第一部分: 基本使用】

先讨论基本的用法,异步开发中,我们一般会遇到三个关键的内容:Promise、async函数、await命令。

1、Promise

Promise可以看做一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。

  • promise异步操作有三种状态:进行中,已成功,已失败。只有异步操作才能改变这个状态。

  • promise状态一旦改变,就不会再发生变化,promise对象改变的两种可能,进行中—>已成功,进行中—>已失败

1.1 基本用法

promise对象是一个构造函数,用来生成promise实例,其中接受的参数是resolve和reject两个函数。

👉🏻 resolve的作用:将promise对象的状态由进行中—>已完成。并将异步操作的结果作为参数传递出去

👉🏻 rejected的作用:将promise对象的状态由进行中—>已失败,并将异步失败的原因作为参数传递出去。

举例:

const promise = new Promise(function(resolve, reject) {  // ... some code  if (/* 异步操作成功 */){    resolve(value);  } else {    reject(error);  }});

1.2 then方法

promise实例生成后,用then方法分别指定resolved状态和rejucted状态的回调函数。

then方法可以接受两个回调函数作为参数,第一个回调函数是当promise对象状态是resolve(已完成)的时候调用,第二个回调函数(可选)是当promise对象状态是reject(已失败)的时候调用。

举例:​​​​​​​

function timeout(ms) {  return new Promise((resolve, reject) => {    setTimeout(resolve, ms, 'done');  });}timeout(100).then((value) => {  console.log(value);});// 结果是done

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。​​​​​​​

getjsON("/post/1.json")// 第一个then方法.then(function(post) {  return getJSON(post.commentURL);})// 第二个then方法.then(function funcA(comments) {  console.log("resolved: ", comments);}, function funcB(err){  console.log("rejected: ", err);});

上面代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用funcA,如果状态变为rejected,就调用funcB。

1.3 catch方法

promise对象中,如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数处理这个错误,另外,then方法指定的回调函数,如果运行中抛出错误也会被catch方法捕获。

promise对象的错误具有“冒泡”性质,会一直向后传,直到被捕获,也就是说,会跳过中间的then函数。

举例:​​​​​​​

 getJSON('/post/1.json') .then(function(post) {  return getJSON(post.commentURL);}).then(function(comments) {  // some code}).catch(function(error) {  // 处理前面三个Promise产生的错误(一个Promise,两个then)});

1.4 finally方法

finally方法用于指定不管promise对象最后状态如何,都会执行的操作。

举例:​​​​​​​

getJSON('/post/1.json') .then(function(post) {  return getJSON(post.commentURL);}).then(function(comments) {  // some code}).finally {// 不论前面三个Promise是异常还是正常,都会执行这里的代码};

1.5 promise.all方法

promise.all方法用于将多个promise实例,包装成一个新的promise实例。
比如:const p = Promise.all([p1, p2, p3]);
Promise.all方法,接受的是一个数组作为参数,其中的元素都是promise实例,如果不是,则会自动将参数转变为promie实例。
p的状态是由它的数组里面的元素决定的,分两种状态
👉🏻 只有p1 p2 p3的状态都变成fulfilled(已完成)的状态才会变成fulfilled(已完成),此时p1 p2 p3的返回值组成一个数组,传递给p的回调函数。
👉🏻 只有p1 p2 p3之中,有一个被rejucted(未完成),p的状态就会变成rejected(未完成),此时第一个被reject的实例的返回值,会传递给p的回调函数。

举例:​​​​​​​

const p1 = new Promise((resolve, reject) => {resolve('hello');}).then(result => result);const p2 = new Promise((resolve, reject) => {throw new Error('报错了');}).then(result => result);Promise.all([p1, p2]).then(result => console.log(result)).catch(e => console.log(e));// Error: 报错了enter code here

1.6 promise.race方法

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例,与promise.all不同的是,race中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。(可以将race视为all的一个反面场景)

举例:​​​​​​​

// p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变const p = Promise.race([p1, p2, p3]);

2、async函数

async函数其实它是Generator函数的语法糖。使异步函数、回调函数在语法上看上去更像同步函数。使得异步操作变得更加方便。基本用法介绍如下:

async返回值是一个promise对象(因此可以使用then方法添加回调函数),当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的内容。

举例:​​​​​​​

async function getStockPriceByName(name) {  const symbol = await getStockSymbol(name);  const stockPrice = await getStockPrice(symbol);  return stockPrice;}getStockPriceByName('goog').then(function (result) {  console.log(result);});

async 函数内部return语句返回的值,会成为then方法调用函数的参数。

举例:​​​​​​​

async function getTitle(url) {  let response = await fetch(url);  let html = await response.text();  return html.match(/<title>([\s\S]+)<\/title>/i)[1];}getTitle('https://lantingshuxu.github.io').then(console.log)

【需要注意:一个async方法内部如果没有使用await命令,那这个方法相当于是一个普通方法】

~~~停留一下~~~

我们再回顾下前文说到的,① async函数在执行的时候,如果遇到await命令就会先返回;② 如果内部没有遇到await命令,这个方法就相当于是普通方法。

结合上面的描述,下面这段代码的打印顺序是什么呢?[认真想一想]​​​​​​​

onBtnClick() {  console.log('onClick start');  this.funcTest();  console.log('onClick end');}async funcTest() {  console.log('async func start');  await this.funcTest2();  console.log('async func end')}async funcTest2() {  console.log('async func2');}onBtnClick(); // 代码执行

公布答案,打印顺序为:​​​​​​​

onClick startasync func startasync func2onClick endasync func end

解释👇🏻:

  • async函数在没有遇到await指令时和普通方法类似,因此,onBtnClick()函数执行时,会紧接着执行funcTest里面的内容。

  • 由于函数执行是从右向左,因此 await this.funcTest2();这段代码会先执行funcTest2(),即打印 async func2,然后再遇到await命令。

  • 由于async函数在遇到await指令后,会先返回,因此,按照顺序先打印了 onClick end日志。

  • 未来await执行完毕后,继续执行后续逻辑,打印了 async func end。

图解如下:

图片

3、await 命令

正常情况下,await命令后面跟着的是一个promise对象,如果不是会自动转化为promise对象,当一个await语句后面的promise变为reject,那么整个函数都会中断执行。【需要注意的是,await命令只能使用在async函数中,普通函数使用await命令将会报错】

举例:​​​​​​​

async function f(){return await 123;}f().then(v =>console.log(v)) // 打印 123async function f2() {  await Promise.reject('出错了');  await Promise.resolve('hello world'); // 不会执行}f2(); 

如果await 后面的异步操作有错,那么等同于async函数返回的promis对象被reject (上文讲promise对象的时候有提到过,冒泡性质)。可以使用try ....catch代码块防止出错。

举例:​​​​​​​

async function f() {  try {    await new Promise(function (resolve, reject) {      throw new Error('出错了');    });  } catch(e) {  }  return await('hello world');}

当然,也可以将多个await命令都放在try..catch结构中。​​​​​​​

async function main() {  try {    const val1 = await firstStep();    const val2 = await secondStep(val1);    const val3 = await thirdStep(val1, val2);    console.log('Final: ', val3);  }  catch (err) {    console.error(err);  }}

【第二部分: 场景讨论】

问:假设有下面一段代码,日志的打印顺序将是什么?并说明为什么会这么执行。​​​​​​​

async function async1() {console.log( 'async1 start' )await async2()console.log( 'async1 end' )}async function async2() {console.log( 'async2' )}console.log( ' start' )setTimeout(() => { console.log( 'setTimeout' )});async1();new Promise(( resolve ) => {console.log( 'promise1' )resolve(1);}).then(() => console.log( 'promise2' ));console.log( ' end' )

在arcTs中,执行的结果将会是如下:

图片

如果前文的基本用法掌握了,基本上我们应该可以得出正确结果。当然,我们还是简单解释下为什么会出现上面的结果。

我们需要总结几点之前的异步结论:

  • async函数如果没有遇到await命令,执行方式与普通函数相同;

  • await xxxFunc()执行时,xxxFunc()会优先于await命令前执行;

  • await命令会让async函数让出当前时间片,后续的指令将在未来拿到时间片后,等到等待的异步函数执行完毕后再执行;

  • promise中的then是在Promise完成之后执行(相当于是await命令),且then会创建一个新的Promise;

另外,setTimeout和普通的Promise异步还有一个区别,setTimeout属于是一个宏任务,而普通的Promise异步属于是微任务,一般情况下执行顺序是:宏任务执行后再执行微任务,然后再执行宏任务。

因此,一般情况下一次函数执行中,Promise异步任务会先于setTimeout执行。

有了上面的结论,我们看上述代码会怎么执行:

  1. console.log( ' start' )是同步代码且顺序靠前,因此最先执行;

  2.  setTimeout(() => { console.log( 'setTimeout' )}); 添加一个宏任务到宏任务队列,执行时机在promise之后。

  3. async1方法虽然是async异步方法,但是在没有遇到await之前,依旧当做同步,因此执行console.log( 'async1 start' ) 打印日志,同时async2() 方法调用也发生在await之前(从右向左看),所以也会执行console.log( 'async2' ) 方法,遇到await之后,方法将会让出当前的执行,因此,await之后的逻辑console.log( 'async1 end' ) 将会在下次时间片去执行。

  4. Promise中的实现与async方法类似,由于console.log( 'promise1' ) 在resolve()之前(resolve()之后可以视为await命令之后的逻辑),因此会打印 promise1 。

  5. 由于Promise的then方法相当于是await之后的逻辑,所以, console.log( 'promise2' ) 也会直接让出时间片。 

  6. console.log( ' end' ) 由于在同步代码中,因此会立即执行。

在主逻辑执行完后,我们知道,还剩下三个异步任务,分别是:

  • async1() 方法中,await之后的逻辑console.log( 'async1 end' ) 

  •  setTimeout(() => { console.log( 'setTimeout' )});

  • Promise中的then方法 .then(() => console.log( 'promise2' ));

知道setTimeout将会把一个宏任务推送到宏任务队列,因此,执行顺序将是在await和Promise微任务之后。

剩下的 async1()方法中的await逻辑和Promise中的then逻辑,将根据实际情况执行(任务顺序不能完全固定)。因此,最终的顺序为:​​​​​​​

  start async1 start async2 promise1  end async1 end  // async1中的await先执行 promise2 setTimeout

或者​​​​​​​

  start async1 start async2 promise1  end promise2 // promise中的then先执行 async1 end setTimeout

【尾巴】

我无法确定本文讨论的东西是不是留言的朋友真正疑惑的点,希望这篇文章能帮助你和更多的人。

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

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

相关文章

Adobe Acrobat Pro和Adobe Acrobat DC有什么区别?

主要区别 Adobe Acrobat Pro&#xff1a; 以单次购买的方式提供&#xff0c;用户需要一次性付费购买&#xff0c;之后即可永久使用该版本。不会频繁更新&#xff0c;通常只在发行新版本时进行更新。 Adobe Acrobat DC&#xff1a; 以订阅方式提供&#xff0c;用户需要每年支付…

《2024 国庆旅游数据洞察:活力与新趋势》

《2024 国庆旅游数据洞察&#xff1a;活力与新趋势》 一、国庆旅游市场整体态势 今年国庆假期&#xff0c;旅游市场的火爆程度令人瞩目。从出行人次来看&#xff0c;嘀嗒出行的国庆顺风出行预测显示&#xff0c;顺风出发高峰日预计为 9 月 29 日&#xff0c;环比 9 月出行峰值…

在Ubuntu 22.04上安装Ollama的两种方式

curl 安装 参考Linux上安装Ollama的官方文档&#xff1a;https://ollama.com/download/linux 在终端执行以下命令即可&#xff1a; curl -fsSL https://ollama.com/install.sh | shdocker 安装 官方 Ollama Docker 镜像可以直接在Docker Hub上进行拉取。 Docker Hub上的ol…

Java 方法的重载

1.重载&#xff1a;在一个类中&#xff0c;方法的函数名相同&#xff0c;但形参不同。 结果&#xff1a; 2&#xff0e;方法重载的规则&#xff1a; &#xff08;1&#xff09;方法名必须相同。&#xff08;例如&#xff1a;重名的人有很多&#xff09; &#xff08;2&#x…

数据库(MySQL):使用命令从零开始在Navicat创建一个数据库及其数据表(二).设置主键自增等特点

前言 在上一节中&#xff0c;主要介绍了 Navicat Premium 17 的使用以及创建一个基础的表格。当时只设置了给数据表补充字段&#xff0c;没有设置给数据表删除字段。现在补充一下。 ALTER TABLE student ADD test int(4); 给名为 student 的数据表添加 test 列&#xf…

正则表达式-“三剑客”(grep、sed、awk)

1.3正则表达式 正则表达式描述了一种字符串匹配的模式&#xff0c;可以用来检查一个串是否含有某种子串&#xff0c;将匹配的子串替换或者从某个串中取出符号某个条件的子串等&#xff0c;在linux中代表自定义的模式模版&#xff0c;linux工具可以用正则表达式过滤文本。Linux…

vavr Java的函数式编程神器-Part1

微信公众号&#xff1a;阿俊的学习记录空间 小红书&#xff1a;ArnoZhang wordpress&#xff1a;arnozhang1994 博客园&#xff1a;arnozhang CSDN&#xff1a;ArnoZhang1994 1. 介绍 Vavr&#xff08;前称 Javaslang&#xff09;是一个为 Java 8 提供的函数式库&#xff0c;…

红外探测算法!!!

一、红外探测的基本原理 红外探测基于红外辐射与物体的热状态之间的关系。物体温度越高&#xff0c;辐射能量越大。红外探测器通过接收物体发出的红外辐射&#xff0c;将其转换为电信号&#xff0c;进而实现对目标的探测和识别。 二、红外探测算法的主要类型 背景差分法&…

[自然语言处理]RNN

1 传统RNN模型与LSTM import torch import torch.nn as nntorch.manual_seed(8)def dm01():参数1&#xff1a;输入向量的维数参数2&#xff1a;隐藏层神经元的个数参数3&#xff1a;隐藏层的层数:return:rnn nn.RNN(5, 6, 1)参数1&#xff1a;句子长度sequence_length参数2&am…

九芯电子NVH/NVF语音芯片OTA升级操作方法

OTA&#xff08;Over-The-Air&#xff09;升级是指通过无线网络远程对设备进行软件升级的过程。对于九芯电子NVH/NVF语音芯片&#xff0c;OTA升级可以通过WiFi模组实现&#xff0c;支持MQTT、HTTP等协议&#xff0c;方便快捷‌。 具体操作步骤如下&#xff1a; 1.进入九芯“智…

计算机毕业设计 基于Django的学生选课系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

处理Java内存溢出问题(java.lang.OutOfMemoryError):增加JVM堆内存与调优

处理Java内存溢出问题&#xff08;java.lang.OutOfMemoryError&#xff09;&#xff1a;增加JVM堆内存与调优 在进行压力测试时&#xff0c;遇到java.lang.OutOfMemoryError: Java heap space错误或者nginx报错no live upstreams while connecting to upstream通常意味着应用的…

重头开始嵌入式第四十七天(硬件 ARM裸机开发 RS232 RS4885 IIC)

目录 一.什么是RS232&#xff1f; 1. 历史背景&#xff1a; 2. 电气特性&#xff1a; 3. 连接器类型&#xff1a; 4. 通信特点&#xff1a; 5. 应用场景&#xff1a; 二.什么是RS485&#xff1f; 1. 电气特性&#xff1a; 2. 通信模式&#xff1a; 3. 传输距离与速率&…

技术路线图用什么画?用这个在线工具轻松完成绘制!

在当今快速发展的技术世界中&#xff0c;技术路线图已成为企业和团队不可或缺的战略规划工具。它不仅能够清晰地展示技术发展方向&#xff0c;还能帮助团队成员、利益相关者和投资者更好地理解和参与技术战略的制定过程。但不可否认的是&#xff0c;创建一个有效的技术路线图并…

如何免费为域名申请一个企业邮箱

背景 做SEO的是有老是会有一些网站来做验证你的所有权&#xff0c;这个时候&#xff0c;如果你域名对应的企业邮箱就会很方便。zoho为了引导付费&#xff0c;有很多多余的步骤引导&#xff0c;反倒是让不付费的用户有些迷茫&#xff0c;所以会写这个教程&#xff0c;按照教程走…

虚幻引擎GAS入门学习笔记(二)

虚幻引擎GAS入门(二) 学习位置UE5.3 GAS入门教程重置版 小明 MVC框架与技能初始化 让一开始创建的蓝图的基础GameplayAbility蓝图继承我们写好的BaseGameplayAbility类 创建一个库函数&#xff0c;写一些常用的函数在里面第一个得到玩家与玩家控制器 获取角色面对目标的方向…

c++11~c++20 thread_local

线程局部存储是指对象内存在线程开始后分配&#xff0c;线程结束时回收且每个线程有该对象自己的实例&#xff0c;简单地说&#xff0c;线程局部存储的对象都是独立各个线程的。实际上这并不是一个新鲜个概念&#xff0c;虽然C一直没因在语言层面支持它&#xff0c;但是很早之前…

Coggle数据科学 | 全球AI攻防挑战赛:金融场景凭证篡改检测 baseline

本文来源公众号“Coggle数据科学”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;全球AI攻防挑战赛&#xff1a;金融场景凭证篡改检测 baseline 赛题名称&#xff1a;全球AI攻防挑战赛—赛道二&#xff08;AI核身-金融场景凭证篡改…

集智书童 | FMRFT 融合Mamba和 DETR 用于查询时间序列交叉鱼跟踪 !

本文来源公众号“集智书童”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;FMRFT 融合Mamba和 DETR 用于查询时间序列交叉鱼跟踪 ! 鱼的生长、异常行为和疾病可以通过图像处理方法进行早期检测&#xff0c;这对工厂水产养殖具有重…

基于云效流水线Flow | 高效构建企业门户网站

基于云效流水线Flow | 高效构建企业门户网站 基于云效流水线Flow | 高效构建企业门户网站企业门户网站方案架构一键部署方案概览部署准备一键部署 部署服务端&#xff08;云效流水线&#xff09;添加流水线源Java构建上传主机部署 资源删除操作体验1&#xff09; 在体验过程中是…