一、前言
首先我们来看一个问题。
let data = null
function getData() {
setTimeout(() => {
data = { name: "张三", age: 20 }
}, 1000);
}
function showData() {
console.log(`我正在展示获取的数据: ${JSON.stringify(data)}`)
}
getData()
showData()//我正在展示获取的数据: null
在以上的代码示例中,我们的本意是想让 showData 函数输出它获取到的数据,可是数据却没有获取到,输出的是null,这是因为 getData 函数是异步代码,它在同步代码执行完之后才执行。那有什么办法解决这个问题呢?
二、async/await的引入
一种显而易见的方法就是回调函数,把 showData 放到 getData 函数里执行,也就是如下所示。
let data = null
function getData() {
setTimeout(() => {
data = { name: "张三", age: 20 };
showData()
}, 1000);
}
function showData() {
console.log(`我正在展示获取的数据: ${JSON.stringify(data)}`);
}
getData(); //我正在展示获取的数据: {"name":"张三","age":20}
第二种方式就是使用Promise对象,注意then(showData)里面showData不要加括号。
let data = null
function getData(){
return new Promise((resolve, reject) =>{
setTimeout(() => {
data = {name: '张三',age: 20}
resolve()
},1000)
})
}
function showData(){
console.log(`我正在展示获取的数据: ${JSON.stringify(data)}`)
}
getData().then(showData) //我正在展示获取的数据: {"name":"张三","age":20}
那么还有什么方法吗?有的,那就是利用async/await。
let data = null
function getData(){
return new Promise((resolve, reject) =>{
setTimeout(() => {
data = {name: '张三',age: 20}
resolve()
},1000)
})
}
function showData(){
console.log(`我正在展示获取的数据: ${JSON.stringify(data)}`)
}
async function handle(){
await getData();
showData();
}
handle(); //我正在展示获取的数据: {"name":"张三","age":20}
并且如果有多个异步函数,就只需要继续await+函数名去调用即可,和原生JS调用函数方法类似,注意await后面所接的代码必须返回Promise对象。接下来我们就聊一聊async/await究竟是什么以及它如何使用?
function getData1(){
return new Promise((resolve, reject) =>{
setTimeout(() => {
console.log({name: '张三',age: 20})
resolve()
},1000)
})
}
function getData2(){
return new Promise((resolve, reject) =>{
setTimeout(() => {
console.log({name: '李四',age: 21})
resolve()
},2000)
})
}
function showData(){
console.log({name: '王五',age: 22})
}
async function handle(){
await getData1();
await getData2();
showData();
}
handle();
//{ name: '张三', age: 20 }
//{ name: '李四', age: 21 }
//{ name: '王五', age: 22 }
三、async/await的基本语法
async/await是JavaScript中处理异步操作的一种方式。它们是ES2017中引入的新关键字,可以让开发人员更方便地处理异步代码。async/await本质上是Promise的语法糖,它们使得异步代码更加易于理解和维护。
async/await的使用需要遵循一定的规则,主要有以下几点:
- 1.在需要异步执行的函数前加上async关键字,这样该函数就会返回一个Promise对象。
- 2.在需要等待异步操作的地方使用await关键字,该关键字会暂停下面代码的执行,直到Promise对象返回结果。
- 3.在使用await关键字时,必须在一个async函数中,否则会抛出语法错误,但是async函数中可以没有await。
1. async返回一个Promise对象
async function asyncFn() {
return 'Hello World!';
}
const result = asyncFn()
console.log(result);
asyncFn().then((value) => {
console.log(value); //Hello World!
})
在上面的代码示例中,在函数前面加上async关键字后,返回的结果不是字符串,而是一个Promise对象,并且状态为fulfilled。async函数内部return语句返回的值,会被then方法回调函数接收到,会成为then方法回调函数的参数。
2. 抛出的错误对象会被catch收到
async 函数内部抛出错误,会导致返回的 Promise 对象变为reject状态,抛出的错误对象会被catch方法回调函数接收到。
async function fn() {
throw new Error('发生错误啦!');
}
fn().then(
(value) => console.log('resolve', value),
(error) => console.log('reject', error) //reject Error: 发生错误啦!
) //(代码会报错)
async function fn() {
await Promise.reject('发生错误啦!');
}
fn()
.then(value => console.log(value))
.catch(error => console.log(error)) //发生错误啦!
任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。下面代码中,第二个await语句是不会执行的,因为第一个await语句状态变成了reject。
async function asyncFn() {
await Promise.reject('出错了');
await Promise.resolve('Hello World!'); //不会执行
}
3. try...catch语句捕获处理抛出的异常
await的promise失败了,就会抛出异常,需要通过try...catch语句捕获处理。 如果我们希望即使前一个异步操作失败,也不要中断后面的异步操作,就可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。
async function asyncFn() {
try {
await Promise.reject('发生错误啦!');
} catch(e) {
console.log(e);
}
return await Promise.resolve('Hello World');
}
asyncFn()
.then(value => console.log(value))
//发生错误啦!
//Hello World
也可以这样写。
async function asyncFn() {
await Promise.reject('发生错误啦!')
.catch(e => console.log(e));
return await Promise.resolve('Hello World!');
}
asyncFn()
.then(value => console.log(value))
//发生错误啦!
//Hello World!
四、async/await考题
我们来看三道题目来加深一下对async/await的理解。
1.题目一
function fn(){
return new Promise((resolve, reject) => {
setTimeout(function(){
console.log(666)
resolve()
},1000)
})
}
async function asyncFn(){
await fn()
console.log(777)
}
asyncFn()
//666
//777
在以上代码示例中,首先我们明确输出顺序是 666 777,因为await会阻止后面代码的执行,等执行完fn函数后才会去执行后续代码。这比较简单,那么如何将async/await转化成Promise形式呢?
function asyncFn(){
return Promise.resolve()
.then(() => {
return fn()
})
.then(() => {
console.log(777)
})
} //666 777
async/await和Promise是两种处理异步编程的方式,它们之间并不是互斥的,而是可以互相转换的。如果你有一个使用async/await编写的异步函数,并且需要将其转换为Promise形式,那么可以使用Promise.resolve方法来实现,所以以上asyncFn函数可以写成两种形式,输出结果一样。
2.题目二
async function fn1(){
return 666
}
async function fn2(){
return new Promise((resolve, reject) => {
resolve(666)
})
}
简单来说,在函数前面加一个async就相当于return了一个new Promise。以上两个函数转化成Promise形式相当于如下。
function fn1(){
return Promise.resolve().then(() => {
return 666
})
}
function fn2(){
return Promise.resolve().then(() => {
return new Promise((resolve, reject) => {
resolve(666)
})
})
}
async function asyncFn() {
let a = 1
let b = 2
await fn1()
let c = 3
await fn2()
let d = 4
await fn3()
return 5
}
第一、将写在await后面的代码放到async创建的那个Promise里面执行。 第二、将写在await下面的代码放到前一个创建的那个Promise对象的.then里面执行。await返回的也是Promise对象,它把await下面的代码放到了await返回的promise的.then里面执行。
function asyncFn() {
return Promise.resolve().then(() => {
let a = 1
let b = 2
return fn1()
})
.then(() => {
let c = 3
return fn2()
})
.then(() => {
let d = 4
return fn3()
})
.then(() => {
return 5
})
}
3.题目三
async function async1(){
console.log('111 开始');
await async2()
console.log('111 结束');
}
async function async2(){
console.log('222');
}
async1()
console.log('666');
//111 开始
//222
//666
//111 结束
比较新的版本的浏览器会对await开辟一个优先通道,把它的优先级提高了。哪怕async2执行可能需要耗时,也会优先执行它,await下面的那行代码依然会被阻塞,所以输出结果如上。
五、async/await的应用
1.读取文件内容
const fs = require('fs');
function readTengWangGe() {
return new Promise((resolve, reject) => {
fs.readFile("./滕王阁序.md", (error, data) => {
//如果失败
if(error) reject(error);
//如果成功
resolve(data);
})
})
}
function readLuShan() {
return new Promise((resolve, reject) => {
fs.readFile("./望庐山瀑布.md", (error, data) => {
//如果失败
if(error) reject(error);
//如果成功
resolve(data);
})
})
}
async function main(){
let tengWangGe = await readTengWangGe();
let luShan = await readLuShan();
console.log(tengWangGe.toString());
console.log(luShan.toString());
}
main();
//落霞与孤鹜齐飞,秋水共长天一色。
//飞流直下三千尺,疑是银河落九天。
这里是简单模拟async/await异步读取文件内容,文件就放在同级目录中,内容分别是落霞与孤鹜齐飞,秋水共长天一色,和飞流直下三千尺,疑是银河落九天。
2.发送Ajax请求
function sendAjax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if(xhr.status === 200 && xhr.status < 300) {
resolve(xhr.response) // 如果成功
}else{
reject(xhr.status) // 如果失败
}
}
};
});
}
async function test(){
const result = await sendAjax("https://www.fastmock.site/mock/7224291cf063beceaa27956d01e64560/keep/recommend")
console.log(JSON.stringify(result));
}
test()
这里是用async/await模拟发送Ajax请求数据。注意以上代码是写在html文件中的,然后用浏览器查看。步骤是:1.创建XMLHttpRequest对象。2.初始化。3.发送。4.事件绑定。发送Ajax请求,返回的结果是Promise对象。
readyState表示就绪状态,xhr.readyState === 4表示请求已经完成并且响应已经准备就绪。status表示请求的HTTP状态码,如果请求成功(状态码为 200),并且状态码小于 300,也就是请求返回的是一个成功的响应(2xx 表示成功),则执行相应的代码块。
六、总结
总之,async/await是一种强大的异步编程技术,它可以使异步代码更加容易理解和维护。通过理解async/await的语法和注意事项,开发人员可以更好地应用这一技术,提高代码的可读性和可维护性。 如果对Promise不了解的,可以去看# 从回调地狱到异步之王:小白初次感受JS Promise的魅力!。
能力一般,水平有限,如有问题欢迎指正,感谢你阅读这篇文章,如果你觉得写得还行的话,不要忘记点赞、评论、收藏哦!祝生活愉快!