• JS之同步异步promise、async、await


    promise异步操作

    Promise是异步编程的一种解决方案

    JavaScript异步与同步解析

    学习promise前我们先来了解下什么是异步?

    基本概念:

    • 消息队列中的任务分为宏任务与微任务;
    • 调用栈也可以称为主线程

    首先我们要知道js是单线程语言,也就是说,它并不能像JAVA语言那样,多个线程并发执行。

    单线程:

    • JavaScript就是一个单线程的语言
    • 为什么js是单线程?如果一个线程在一个节点中添加内容,另一个线程要删除这个节点。所以为了不必要的麻烦,js就是一门单线程语言。

    先讲下什么是同步。同步指的是代码一行一行依次执行,下面的代码必须等待上面代码的执行完成。当遇到一些耗时比较长的任务时,比如网络请求就容易发生阻塞,必须等数据请求过来后才能执行后面的操作。

    image-20231012091327982

    那么这里异步执行是在请求数据的同时还能执行后面的任务。异步是非阻塞的,异步逻辑与主逻辑相互独立,主逻辑不需要等待异步逻辑完成,而是可以立刻继续下去

    image-20231012091440179

    JS的异步执行分析:

    • 拿现实生活来举例,比如一个人在家(将一个人比作单线程),你既要煮饭又要炒菜。
    • 这里我们把煮饭算作一个异步的任务,因为煮饭是一个比较耗时的任务(一般像比较耗时或不确定执行时间的任务,比如定时器,网络请求,事件执行 都是异步执行),其次你没炒完菜是不会去吃饭的(也就是主线程任务没有完成,是不会执行异步任务的)。
    • 那么你可以怎么做呢?你可以把煮饭的任务交给电饭煲处理。
    • 先把米放入电饭煲交给了电饭煲处理,再去炒菜,炒完菜再去把煮好的饭取出来。
    • 将饭交给电饭煲处理相当于开启了一个异步的任务,电饭煲就是处理这个异步任务的模块。饭煮好了会自动跳转,这就相当于异步任务被对应的模块解析好了会自动放入消息队列,等待事件循环调入主线程执行(前提是主线程任务全部执行完毕)
    • 主线程任务执行完成,会通过不断的循环消息队列,来执行其中的任务
    • 也就是你把炒菜完了,你就会不断的观察饭是否跳转(也就是循环消息队列看是否有任务),如果有就把饭装到碗里开始吃饭,此时任务就全部完成。
    • 但是干活的始终还是一个人,这就是单线程的异步执行过程。
      20210610125743317
    console.log("遇到煮饭任务,将饭放入电饭煲");
    //使用setTimeout模拟煮饭
    setTimeout(()=>{
    	console.log("饭已经煮熟,等待饭被取出");
    },0);
    console.log("开始炒菜任务");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 你以为的执行顺序:“遇到煮饭任务,将饭放入电饭煲 ” -> “饭已经煮熟,等待饭被取出 -> ”开始炒菜任务“
    • 但是你想想这样符合逻辑吗,你会等饭熟练才开始炒菜吗?
    • 最终执行顺序:“遇到煮饭任务,将饭放入电饭煲 ” -> “开始炒菜任务” -> “饭已经煮熟,等待饭被取出”
    • 显然js都知道你认为的执行顺序是不符合逻辑的。这里setTimeout就是一个异步任务,其中的箭头函数就是异步完成后回调的函数。

    解释疑惑:
    JavaScript既然是单线程语言,那么为什么同时可以执行多个任务(同时煮饭炒菜)?

    • 你可能会想这不是废话吗,煮饭交给电饭煲了啊。

    • 确实没错,煮饭任务交给电饭煲了,那么在JS中是谁去处理这些异步的任务呢?

      • 前面异步任务分析有说到,异步任务会被对应模块解析(饭被电饭煲模块解析)。

      • 那么这就和宿主有关系了,js一般都是运行在游览器上(当然有node.js),也就是寄生在游览器上,那么宿主就是游览器。所以是宿主提供的模块去处理这些异步任务,使得JS可以实现与其他语言类似的多线程操作。

    补充异步任务执行顺序:

    • 而常见的promise,async,await 执行放入的是微任务队列中,主线程的代码执行完后,会优先循环微任务队列的代码,再是宏任务队列。
    • 主线程 > 微任务 > 宏任务
    • 注意!!宏任务队列与微任务队列的任务都是谁先进入队列谁先执行。

    网络异步请求

    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    image-20231012183016051

    异步任务的影响

    所以从上可以看出异步的执行是依赖于回调函数,那么在进行异步操作时回调函数会带来什么影响呢?那就是回调地狱。

    回调地狱指的是:回调函数嵌套回调函数,形成的多层嵌套。

    例子:查询三次网络请求,请求成功一次才会发起下一个请求

    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script>
        $.ajax({
            type: "post",
            url: "https://tenapi.cn/v2/yiyan",
            success(data) {
                console.log("第一次成功查询信息:", data);
                $.ajax({
                    type: "post",
                    url: "https://tenapi.cn/v2/yiyan",
                    success(data) {
                        console.log("第二次成功查询信息", data);
                        $.ajax({
                            type: "post",
                            url: "https://tenapi.cn/v2/yiyan",
                            success(data) {
                                console.log("第三次成功查询信息", data);
                            },
                            error(error) {
                                console.log("第三次成功信息出现异常了:" + error);
                            }
                        });
                    },
                    error(error) {
                        console.log("第二次成功信息出现异常了:" + error);
                    }
                });
            },
            error(error) {
                console.log("第一次成功信息出现异常了:" + error);
            }
        });
    
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    image-20231012185118570

    Promise的用法

    异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理且更强大

    实例化Promise对象

    var promise = new Promise(传一个函数);
    var promise = new Promise(function (resolve, reject) {
       if (/* 异步操作成功 */) {
          resolve(value);
        } else {
          reject(error);
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • shi

    三个状态

    有三个状态:

    • pending [待定] 初始状态
    • fulfilled [实现] 操作成功
    • rejected [拒绝] 操作失败
    var promise = new Promise(function (resolve, reject) {
            if (false) {
                resolve('success');
            } else {
                reject('fail');
            }
        });
        promise.then(res => {
            console.log(res) // 成功 resolve('success')
        }).catch(err => {
            console.log(err) // 失败 reject('fail');
        });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20231012190329560

    当promise状态发生改变,就会触发then()里的响应函数处理后续步骤

    状态改变,只有两种可能:

    • 从pending变为fulfilled
    • 从pending变为rejected

    相关API

    Promise.all

    Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值

    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    image-20231012202520115

    Promise.race

    顾名思义, Promse.race就是赛跑的意思,意思就是说, Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态

    <script>
       let p1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('success')
            }, 1000)
        })
        let p2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('failed')
            }, 500)
        })
        Promise.race([p1, p2]).then((result) => {
            console.log(result)
        }).catch((error) => {
            console.log(error) // 打开的是 'failed'
        })
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    image-20231012202807074

    使用Promise解决回调地狱

    如上代码就是产生了回调地狱,当代码过多会非常复杂。如下就是使用一种优雅的方式(promise)来解决如上的问题

    • 解决刚刚那个发送三次请求案例
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script>
        //使用Promise解决回调地狱
        function post(url,n) { //自己定义一个方法整合一下
            return new Promise((resolve, reject) => {
                $.ajax({
                    url: url,
                    success: function (data) {
                        resolve(data);
                    },
                    error: function (err) {
                        reject(`${n}次调用失败`+err)
                    }
                })
            });
        }
        post("https://tenapi.cn/v2/yiyan",1)
            .then((data) => {
                console.log("第一次成功查询信息:", data)
                return post("https://tenapi.cn/v2/yiyan",2);
            })
            .then((data) => {
                console.log("第二次成功查询信息:", data)
                return post("https://tenapi.cn/v2/yiyan",3);
            })
            .then((data) => {
                console.log("第三次成功查询信息", data)
            })
            .catch((err) => { //失败的话catch
                console.log( err)
            });
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 这样代码就显的非常清楚,不想之前那样非常难懂

    image-20231012205206442

    总结

    Promis就是对异步操作进行封装,其中的思想就是不希望你在回调函数中处理需要执行的代码,而是把回调执行的代码放入对应的区域也就是then()或catch()方法中处理,然后通过resolve()或reject()来调用。将代码分离后做的时链式的调用,你可以这样理解一个then或一个catch就是一个链条的连接点。一般有异步操作我们都要使用promise对异步操作进行封装,养成良好的习惯。

    JS的同步执行解析:

    • 代码由上至下依次执行。前面任务在执行,后面代码必须排队等待。
    • 就如上面的例子如果不做异步处理,让任务同步执行就会一直卡在做饭的地方,等饭煮好了才能去炒菜。

    Async / await

    也是用来处理异步的,其实是Generator 函数的改进,背后原理就是promise

    Async

    <script>
    async function f1() {
        return "abc";
       
    }
    console.log(f1())
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    image-20231012214027122

    // async
    async function f1() {
        return "abc";
        // 自动包装成promise对象
        // 与下面两种等价
        // return Promise.resolve('abc');
        // return new Promise((resolve) => { resolve('abc') });
    }
    f1().then((data)=>{
       console.log("执行了异步",data)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20231012214200026

    await

    function f4() {
       setTimeout(() => {
         console.log("222")
       }, 3000)
       console.log("333")
    }
    function f5() {
       console.log("111")
       f4(); 
       console.log("444")
    }
    f5()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20231012214539743

    async function f4() {
         await new Promise(function (resolve, reject) {
             setTimeout(() => {
               resolve()
             }, 3000)
         }).then((data)=>{
             console.log("222")
         })
         console.log("333")
    }
    function f5() {
         console.log("111")
         f4();
         console.log("444")
    }
    f5()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    image-20231012221139473

    async function f4() {
        await new Promise(function (resolve, reject) {
            setTimeout(() => {
               resolve()
            }, 3000)
        }).then((data)=>{
           console.log("222")
        })
        console.log("333")
    }
    async function f5() {
        console.log("111")
        await f4();
        console.log("444")
    }
    f5()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    image-20231012221246600

    异常处理

    async function f3() {
       return Promise.reject('sss');
       // return Promise.resolve('abc')
    }
    async function f5() {
       try {
         var c = await f3().then((data)=>{ console.log(data)});
       } catch (e) {
         console.log(e)
       }
       //如果await 是reject,后面代码就不会执行,要加上try catch才会执行
    }
    f5()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    image-20231012221614568

  • 相关阅读:
    接口测试之文件下载
    CentOS 7 安装 MySQL8.0
    spring boot项目修改配置文件后运行报错
    C++学习记录——삼십삽 STL空间配置器
    一文学完Linux Shell编程,比书都好懂
    Qt 将qsqlite数据库中的数据导出为Excel表格
    Commvault+XSKY 推出基于 Object Lock 的防勒索病毒联合方案
    【算法挨揍日记】day09——704. 二分查找、34. 在排序数组中查找元素的第一个和最后一个位置
    POI 中 Excel设置列的格式
    亚马逊云科技面向 macOS 的 Amazon 云服务器 EC2 M1 Mac 实例
  • 原文地址:https://blog.csdn.net/qq_50985215/article/details/133800566