logo
积分中心
异步场景: promise、async函数与await命令介绍
头像 蓝亭书序 2026-10-28 17:04:47    发布
0 浏览 0 点赞 0 收藏

在鸿蒙的开发中,我们时常会遇到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 start
async func start
async func2
onClick end
async 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)) // 打印 123

async 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



©本站发布的所有内容,包括但不限于文字、图片、音频、视频、图表、标志、标识、广告、商标、商号、域名、软件、程序等,除特别标明外,均来源于网络或用户投稿,版权归原作者或原出处所有。我们致力于保护原作者版权,若涉及版权问题,请及时联系我们进行处理。
分类
HarmonyOS
地址:北京市朝阳区北三环东路三元桥曙光西里甲1号第三置业A座1508室 电话:13391790444或(010)62178877
版权所有:电脑商情信息服务集团 北京赢邦策略咨询有限责任公司
声明:本媒体部分图片、文章来源于网络,版权归原作者所有,我司致力于保护作者版权,如有侵权,请与我司联系删除

京ICP备:2022009079号-2

京公网安备:11010502051901号

ICP证:京B2-20230255