Promise学习(四)异步编程的终极解决方案async + await:用同步的方式去写异步代码


前言

    本文是通过尚硅谷老师的Promise教程阮一峰ES6教程以及一些其他的网络资源来进行总结学习。



1. async 函数

    ES2017 标准引入了 async 函数,使得异步操作变得更加方便。 async 函数是使用 async 关键字声明的函数。 async 函数是AsyncFunction构造函数的实例, 并且其中允许使用 await 关键字。asyncawait 关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise

    async 是一个加在函数前的修饰符,被 async 定义的函数会默认返回一个Promise对象 resolve 的值。因此对 async 函数可以直接 then,返回值就是then方法传入的函数。

  • 函数的返回值为 promise 对象
  • promise 对象的结果由 async 函数执行的返回值决定
async function main() {
    //1. 如果返回值是一个非Promise类型的数据
    // return 521;    // 成功
    //2. 如果返回的是一个Promise对象
    // return new Promise((resolve, reject) => {
    //     // resolve('ok');  // 成功
    //     reject('err');  // 失败
    // });
    //3. 抛出异常
    throw "失败了";  // 失败  
};

let result = main();
console.log(result);

在这里插入图片描述

可以看出执行的结果由 async 函数的返回值状态来决定


 

2. await表达式

    await 也是一个修饰符,只能放在async定义的函数内。可以理解为等待await 后面可以跟任何的JS 表达式。虽然说 await 可以等很多类型的东西,但是它最主要的意图是用来等待 Promise 对象的状态被 resolved。如果await的是 promise对象会造成异步函数停止执行,并且等待 promise 的解决,如果等的是正常的表达式则立即执行

注意:

1. await 可以理解为是 async wait 的简写。await 必须出现在 async 函数内部,不能单独使用。 async函数可以单独使用
2. await 主要是对Promise对象成功的结果获取
3. 如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理

  • await 右侧的表达式一般为 promise 对象, 但也可以是其它的值
  • 如果表达式是 promise 对象, await 返回的是 promise 成功的值
  • 如果表达式是其它值, 直接将此值作为 await 的返回值

成功状态:

const main = async () => {
    let p = new Promise((resolve, reject) => {
        resolve('ok');
    });
    // 1. 右侧为promise的情况
    let res1 = await p;
    console.log(res1);   // ok
    //2. 右侧为其他类型的数据
    let res2 = await 20;
    console.log(res2);  // 20 
}

main();

在这里插入图片描述

 
失败状态(try...catch 捕获处理):

const main = async () => {
    let p = new Promise((resolve, reject) => {
        reject('err了');
    });

    // 如果promise是失败的状态
    try {
        let res3 = await p;
    } catch(reason) {
        console.log(reason);
    } 
}

main();

在这里插入图片描述



3. 解决异步编程的终极方案 async + await

先说一下Promise中各个任务状态的理解:

  • Promise==>异步
  • await==>异步转同步
  • async==>同步转异步

    举一个回调地狱的异步例子:(回调地狱是指:多层嵌套函数,函数的返回值是下一个函数的执行条件。)

setTimeout(() => {
   console.log('1');
   setTimeout(() => {
       console.log('2');
       setTimeout(() => {   
           console.log('3');
       }, 1000);
   }, 2000);
}, 3000);

它会每隔一秒依次输出'1''2''3' ,回调地狱的缺点是不便于阅读 不便于异常处理

解决方案:

  1. 原生Promise 链式调用的方式在前面的 Promise学习(一)Promise是什么?怎么用?回调地狱怎么解决?中有提到过,感兴趣的可以去看看哦!
  2. 终极方案 async + await
const test1 = n => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(n)
        }, 1000);
    });
}

const test = async () => {
    console.log(await test1(1));
    console.log(await test1(2));
    console.log(await test1(3));
}
test();

这样做的好处是:结构清晰便于阅读方便异常处理(如果抛出了异常,可以用 try...catch 处理异常),这里为了方便理解,直接用的成功的状态。


 

4. async 和 await 结合实践1:读取文件信息

    小案例1:读取 1.html 2.html 3.html 三个文件内容,有两种方法,一是回调函数形式实现,二是 async + await 实现。

1. 回调函数形式:

// 引入fs模块
const fs = require('fs');

 //回调函数的方式
fs.readFile('./resource/1.html', (err, data1) => {
    if (err) throw error;
    fs.readFile('./resource/2.html', (err, data2) => {
        if (err) throw error;
        fs.readFile('./resource/3.html', (err, data3) => {
            if (err) throw error;
            console.log(data1 + data2 + data3);
        });
    });
});

  打开集成终端输入 node .\当前要运行的文件名 ,即可以读取三个文件内容。
在这里插入图片描述
 

2. async + await 实现:

// 引入fs模块
const fs = require('fs');
//引入 util 模块  
// util.promisify 方法可以将函数直接变成promise的封装方式,不用再去手动封装
const util = require('util');
const mineReadFile = util.promisify(fs.readFile);

//async 与 await 实现
const main = async () => {
        try{
        //读取第一个文件的内容
        let data1 = await mineReadFile('./resource/1xxx.html');  // 错误文件
        let data2 = await mineReadFile('./resource/2.html');
        let data3 = await mineReadFile('./resource/3.html');
        console.log(data1 + data2 + data3);
    }catch(e){
        console.log(e);    // 错误会提示
        console.log(e.code);  // 错误中的一个属性
    }
}

main();

如果文件名都是正确的会得到和回调函数形式实现的一样的结果,当我们输入错误的文件名时,最后可以得到try...catch 处理的错误的相关信息

在这里插入图片描述

 

5. async 和 await 结合实践2:结合Ajax获取接口信息

    小案例2:结合Ajax获取接口信息async + await 实现。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
    <button id="btn">点击获取段子</button>

    <script>
        const sendAJAX = url => {
            return new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest();
                xhr.responseType = 'json';
                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);
                        }
                    }
                }
            })
        }

        $("#btn").click(async () => {
            // 获取接口文字信息
            let datas = await sendAJAX('https://pagead2.googlesyndication.com/getconfig/sodar?sv=200&tid=gda&tv=r20220718&st=env');
            console.log(datas);
        });
    </script>
</body>
</html>

在这里插入图片描述

注意接口的跨域问题

这里提一下跨域问题,因为不会自己写接口,网上找的接口很多都有跨域问题,所以我们在选择接口是要看清网络中有无CORS错误的字眼,跨域的接口是不能成功返回数据的。

在这里插入图片描述


至此,Promise学习篇暂时以完结,如果还有补充后续会继续更新,如果觉得对你有帮助的话请继续支持哦!

Authors: min
时间: 2022年7月27日