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 的使用场景主要包括以下几个方面:
-
串行处理多个异步操作:当需要按顺序执行多个异步操作时,async/await 可以确保一个操作完成后才执行下一个操作,避免了回调地狱(callback hell)和链式
.then()
方法带来的嵌套层次过深的问题。例如,在爬取网页内容时,需要按顺序请求网页中的图片,可以使用 async/await 确保每个请求按顺序执行。 -
并发请求:在某些情况下,可能需要同时发起多个请求以提高效率。虽然 async/await 本身不支持并发,但可以通过其他方式(如使用 Promise.all)来实现并发请求。例如,同时获取一个人的学校地址和家庭住址,然后再获取详细信息。
-
错误处理: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 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
小结:async 和 await是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博客