JS | ES中的异步方法 async /await 详解

ES8提出的async 和 await 关键字两种语法结合可以让异步代码像同步代码一样,可以解决promise对象不断使用then方法的代码不简洁问题。

async、await 语法是ES8(ECMAScript 2017)里面的新语法,在2016年被提出,它的作用就是 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。它可以很好的替代promise 中的then。async 函数返回一个 Promise 对象,可以使用then 方法添加回调函数。当函数执行的时候,一旦遇到await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。 

在async/await之前,我们有如下三种方式写异步代码:

一、嵌套回调

二、以Promise为主的链式回调

三、使用Generators

但是,这三种写起来都不够优雅,ES8做了优化改进,async/await应运而生,async/await相比较Promise 对象then函数的嵌套,与 Generator 执行的繁琐(需要借助第三方库co才能自动执行,否则得手动调用next() ), Async/Await 可以让你轻松写出同步风格的代码同时又拥有异步机制,更加简洁,逻辑更加清晰。

async 和 await 是 ES2017 新增两个关键字,它们借鉴了 ES2015 中生成器在实际开发中的应用,目的是简化 Promise api 的使用,并非是替代 Promise。

async/await的特点

1. async/await更加语义化,async 是“异步”的简写,async function 用于声明一个 function 是异步的; await,可以认为是async wait的简写, 用于等待一个异步方法执行完成;

2. async/await是一个用同步思维解决异步问题的方案(等结果出来之后,代码才会继续往下执行)

3. 可以通过多层 async function 的同步写法代替传统的callback嵌套

async/awiat的使用规则

1. async 返回的是一个 Promise 成功的对象,await 就是等待这个 promise 的返回结果后,再继续执行。

2. await 等待的是一个 Promise 对象,后面必须跟一个 Promise 对象,但是不必写 then (),直接就可以得到返回值。

但注意,await 所等待的 promise 对象,他的最终状态一定是 resolve 的(当然也可以不是 resolve ,只不过不会执行后面的代码罢了),否则不会执行await 后面的代码,也就是不会去执行所谓的 then() ;  

 async function 语法  

目的是简化在函数的返回值中对Promise的创建。async 用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置,被修饰函数的返回结果一定是 Promise 对象。

1、自动将常规函数转换成Promise,返回值也是一个Promise对象

2、只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数

3、异步函数内部可以使用await

async function name([param[, param[, ... param]]]) { statements }

name: 函数名称。

param:  要传递给函数的参数的名称

statements: 函数体语句。

返回值:返回的Promise对象会以async function的返回值进行解析,或者以该函数抛出的异常进行回绝。

async function add1(x) {
  // async声明add1是一个异步函数
  let a = 1;
  return x + a;
}

add1(10); // 执行add1函数,传递参数10

执行结果,如下:

可以看出add1(10)的返回值是一个resolved的Promise

  await 语法  

await关键字必须出现在async函数中!!!!await用在某个表达式之前,如果表达式是一个Promise,则得到的是thenable中的状态数据。

1、await 放置在Promise调用之前,await 强制后面的代码等待,直到Promise对象resolve,得到resolve的值作为await表达式的运算结果。

2、await只能在async函数内部使用,用在普通函数里就会报错

[return_value] = await expression;

expression:  一个 Promise  对象或者任何要等待的值。

返回值:返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。

function pm() {
  // pm函数返回一个promise对象
  return new Promise((resolve, reject) => {
    resolve("promise value:1");
  })
};
async function test() {
  let a = await pm(); // 等待一promise对象,返回promise对象的处理结果 (这里是resolve的'promise value:1')
  let b = await "not promise value: 2"; // 等待一个字符串,立即返回该值本身
  console.log(a); // 打印 a的值
  console.log(b); // 打印 b 的值
  return a + b;
}

在开发者工具里,测试执行结果,如下: 

async/awiat的错误处理

在async函数里,无论是Promise reject的数据还是逻辑报错,都会被默默吞掉,所以最好把await放入try{}...catch{}中,catch能够捕捉到Promise对象rejected的数据或者抛出的异常

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject("error");
    }, ms); //reject模拟出错,返回error
  });
}
async function asyncPrint(ms) {
  try {
    console.log("start");

    await timeout(ms); //这里返回了错误

    console.log("end"); //所以这句代码不会被执行了
  } catch (err) {
    console.log(err); //这里捕捉到错误error
  }
}
asyncPrint(1000);

如果不用try / catch的话,也可以像下面这样处理错误(因为async函数执行后返回一个promise)

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => { reject('error') }, ms);  //reject模拟出错,返回error
  });
}
async function asyncPrint(ms) {
  console.log('start');
  await timeout(ms)
  console.log('end');  //这句代码不会被执行了
}
asyncPrint(1000).catch(err => {
  console.log(err); // 从这里捕捉到错误
});

如果你不想让错误中断后面代码的执行,可以提前截留住错误,像下面

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject("error");
    }, ms); //reject模拟出错,返回error
  });
}
async function asyncPrint(ms) {
  console.log("start");
  await timeout(ms).catch((err) => {
    // 注意要用catch
    console.log(err);
  });
  console.log("end"); //这句代码会被执行
}
asyncPrint(1000);

async/awiat的使用场景

多个await命令的异步操作,如果不存在依赖关系(后面的await不依赖前一个await返回的结果),用Promise.all()让它们同时触发

function test1() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1);
    }, 1000);
  });
}

function test2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(2);
    }, 2000);
  });
}

async function exc1() {
  console.log("exc1 start:", Date.now());

  let res1 = await test1();

  let res2 = await test2(); // 不依赖 res1 的值

  console.log("exc1 end:", Date.now());
}

async function exc2() {
  console.log("exc2 start:", Date.now());

  let [res1, res2] = await Promise.all([test1(), test2()]);

  console.log("exc2 end:", Date.now());
}

exc1();

exc2();

exc1 的两个并列await的写法,比较耗时,只有test1执行完了才会执行test2。你可以在浏览器的Console里尝试一下,会发现exc2的用Promise.all执行更快一些

小结:async/await 的使用场景主要包括以下几个方面‌:

  1. 串行处理多个异步操作‌:当需要按顺序执行多个异步操作时,async/await 可以确保一个操作完成后才执行下一个操作,避免了回调地狱(callback hell)和链式 .then() 方法带来的嵌套层次过深的问题。例如,在爬取网页内容时,需要按顺序请求网页中的图片,可以使用 async/await 确保每个请求按顺序执行‌。

  2. 并发请求‌:在某些情况下,可能需要同时发起多个请求以提高效率。虽然 async/await 本身不支持并发,但可以通过其他方式(如使用 Promise.all)来实现并发请求。例如,同时获取一个人的学校地址和家庭住址,然后再获取详细信息‌。

  3. 错误处理‌:async/await 可以搭配 try/catch 语句来处理异步操作中的错误,使得错误处理更加方便和直观。例如,在获取数据时,可以使用 try/catch 来捕获和处理可能出现的错误‌。

async/await 的基本概念和语法‌:

  • async 关键字‌:用于声明一个函数为异步函数。异步函数总是返回一个 Promise,即使没有明确地返回一个 Promise,也会隐式地将其返回值包装在一个 resolved Promise 中。如果异步函数返回了一个非 Promise 值,它会被转换为 resolved Promise,返回值作为 resolve 的参数‌。

  • await 关键字‌:只能在 async 函数内部使用。await 后跟一个表达式,这个表达式的值通常是一个 Promise。当遇到 await 表达式时,JavaScript 会暂停异步函数的执行,等待 Promise 解决(resolved)或拒绝(rejected),然后恢复执行并返回 Promise 的结果或抛出错误‌。

async/await 的优点‌:

  • 代码清晰‌:使得异步代码看起来更像同步代码,以线性的方式编写代码,不需要使用回调函数或者链式调用 .then() .catch()

  • 错误处理方便‌:可以结合 try/catch 语句来处理异步操作中的错误,使得错误处理更加方便和直观‌。

通过这些使用场景和优点,可以看出 async/await 在处理异步操作时具有显著的优势,能够提高代码的可读性和维护性。

await返回打印测试

await后面的promise状态不是resolve的输出结果

async function async1() {
  console.log("async1 start");
  await new Promise((resolve) => {
    console.log("promise1");
  });
  console.log("async1 success");
  return "async1 end";
}
console.log("srcipt start");
async1().then((res) => console.log(res));
console.log("srcipt end");

/* await后面的promise状态不是resolve的输出结果
"srcipt start"
"async1 start"
"promise1"
"srcipt end"
*/

 在开发者工具里,测试执行结果,如下: 

这里我们可以看到:在 async1 中 await 后面的 Promise 是没有返回值的,也就是它的状态始终是 pending 状态,所以在 await 之后的内容是不会执行的,包括 async1 后面的 .then。 

await后面的promise状态是resolve的输出结果

async function async1() {
  console.log("async1 start");
  await new Promise((resolve) => {
    console.log("promise1");
    resolve();
  });
  console.log("async1 success");
  return "async1 end";
}
console.log("srcipt start");
async1().then((res) => console.log(res));
console.log("srcipt end");

/*await后面的promise状态是resolve的输出结果
"srcipt start"
"async1 start"
"promise1"
"srcipt end"
"async1 success"
"async1 end"
*/

 在开发者工具里,测试执行结果,如下:  

async/await的兼容性 

在自己的项目中使用

通过 babel 来使用。只需要设置 presets 为 stage-3 即可。

async/await的总结

解决函数回调经历了几个阶段, Promise 对象, Generator 函数到async函数。async函数目前是解决函数回调的最佳方案。很多语言目前都实现了async,包括Python ,java spring,go等。

async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。


小结:asyncawait是ES2017(即ES8)提出的前端异步编程特性。‌

在ES7(ECMAScript 2017)中,async/await被引入,这是一个重要的前端异步编程特性,使得异步代码的书写和理解更加直观和易于管理,几乎让异步代码看起来像同步代码一样简单。这个特性通过Promise对象来实现,允许你以同步的方式编写异步代码,从而提高代码的可读性和可维护性。在ES8中,async/await的引入,使得处理异步操作变得更加简单和直观,减少了回调嵌套的复杂性,提高了代码的质量和开发效率。尽管async/await是在ES8中正式提出,但它们的使用已经广泛影响了后续的ES版本和JavaScript的开发实践。

此外,ES9引入了for await...of循环,这是一个专门用于处理异步迭代器的语法,进一步扩展了async/await的功能和应用场景。for await...of循环允许开发者更方便地处理异步数据流,如读取文件、网络请求等返回的数据流,使得异步编程的模型更加完善和灵活。

综上所述,async和await作为前端异步编程的重要特性,虽然在ES7(ES2017)中被引入,但它们的影响力和应用在ES8及以后的版本中得到了进一步的扩展和完善‌。


● 参考资料 ●

—— JS | Promise对象的使用详解 | ES6新增对象 - Promise使用方法详解 - CSDN博客 ——

—— JS | ES6 新增函数类型——Generator 函数使用方法详解-CSDN博客 ——

async/await异步应用的常用场景-CSDN博客 | JS中async/await常用场景 - 博客园

ES6-Promise简介、ES7 新特性及ES8新特性async和await -CSDN博客

es7中的async、await使用方法示例详解_await request-CSDN博客

async与await总结 | async和await - 简书 | ES6—async和await - CSDN博客

async和await - CSDN博客 | 深入理解ES7的async/await - CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

儒雅的烤地瓜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值