ES6学习笔记——Promise(1)

8.异步处理之Promise

8.0 [回顾]事件循环

JS运行的环境称之为宿主环境。

执行栈:call stack,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。

JS引擎永远执行的是执行栈的最顶部。

异步函数:某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数称之为异步函数。比如事件处理函数。异步函数的执行时机,会被宿主环境控制。

浏览器宿主环境中包含5个线程:

  1. JS引擎:负责执行执行栈的最顶部代码

  2. GUI线程:负责渲染页面

  3. 事件监听线程:负责监听各种事件

  4. 计时线程:负责计时

  5. 网络线程:负责网络通信

当上面的线程发生了某些事请,如果该线程发现,这件事情有处理程序,它会将该处理程序加入一个叫做事件队列的内存。当JS引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。

JS引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。

事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:

  1. 宏任务(队列):macroTask,计时器结束的回调、事件回调、http回调等等绝大部分异步函数进入宏队列

  2. 微任务(队列):MutationObserver,Promise产生的回调进入微队列

MutationObserver用于监听某个DOM对象的变化

当执行栈清空时,JS引擎首先会将微任务中的所有任务依次执行结束,如果没有微任务,则执行宏任务。

console.log("a")

setTimeout(() => {
    console.log("b")
}, 0);

for (let i = 0; i < 1000; i++) {
    console.log("c")
}
    //执行顺序  a  1000个c   b 

MutationObserver例子:

 <ul id="container">
 </ul>
 <button id="btn">点击</button>
 let count = 1;
        const ul = document.getElementById("container");
        document.getElementById("btn").onclick = function A() {
            setTimeout(function C() {
                console.log("添加了一个li")
            }, 0);
            var li = document.createElement("li")
            li.innerText = count++;
            ul.appendChild(li);
        }

        //监听ul
        const observer = new MutationObserver(function B() {
            //当监听的dom元素发生变化时运行的回调函数
            console.log("ul元素发生了变化")
        })
        //监听ul
        observer.observe(ul, {
            attributes: true, //监听属性的变化
            childList: true, //监听子元素的变化
            subtree: true //监听子树的变化
        })
        //取消监听
        // observer.disconnect();
// 当点击按钮后会先执行打印 ul元素发生了变化, 后打印  添加了一个li; 因为在添加li那里有一个定时器,执行时会把该执行内容放入宏队列,然后MutationObserver的回调会放入微队列,执行事件队列时会优先把微队列任务放入JS引擎执行,然后才会执行宏队列任务。
8-1. 事件和回调函数的缺陷

我们习惯于使用传统的回调或事件处理来解决异步问题

事件:某个对象的属性是一个函数,当发生某一件事时,运行该函数

dom.onclick = function(){

}

回调:运行某个函数以实现某个功能的时候,传入一个函数作为参数,当发生某件事的时候,会运行该函数。

dom.addEventListener("click", function(){

})

本质上,事件和回调并没有本质的区别,只是把函数放置的位置不同而已。

一直以来,该模式都运作良好。

直到前端工程越来越复杂…

目前,该模式主要面临以下两个问题:

  1. 回调地狱:某个异步操作需要等待之前的异步操作完成,无论用回调还是事件,都会陷入不断的嵌套
  2. 异步之间的联系:某个异步操作要等待多个异步操作的结果,对这种联系的处理,会让代码的复杂度剧增

回调地狱例子1:

 <p>
        <button id="btn1">按钮1:给按钮2注册点击事件</button>
        <button id="btn2">按钮2:给按钮3注册点击事件</button>
        <button id="btn3">按钮3:点击后弹出hello</button>
    </p>
const btn1 = document.getElementById("btn1"),
            btn2 = document.getElementById("btn2"),
            btn3 = document.getElementById("btn3");
        btn1.addEventListener("click", function() {
            //按钮1的其他事情
            btn2.addEventListener("click", function() {
                //按钮2的其他事情
                btn3.addEventListener("click", function() {
                    alert("hello");
                })
            })
        })

回调地狱例子2:

 /*
      邓哥心中有三个女神
      有一天,邓哥决定向第一个女神表白,如果女神拒绝,则向第二个女神表白,直到所有的女神都拒绝,或有一个女神同意为止
      用代码模拟上面的场景
        */
        function biaobai(god, callback) {
            console.log(`邓哥向女神【${god}】发出了表白短信`);
            setTimeout(() => {
                if (Math.random() < 0.1) {
                    //女神同意拉
                    //resolve
                    callback(true);
                } else {
                    //resolve
                    callback(false);
                }
            }, 1000);
        }

        biaobai("女神1", function(result) {
            if (result) {
                console.log("女神1答应了,邓哥很开心!")
            } else {
                console.log("女神1拒绝了,邓哥表示无压力,然后向女神2表白");
                biaobai("女神2", function(result) {
                    if (result) {
                        console.log("女神2答应了,邓哥很开心!")
                    } else {
                        console.log("女神2十分感动,然后拒绝了邓哥,邓哥向女神3表白");
                        biaobai("女神3", function(result) {
                            if (result) {
                                console.log("女神3答应了,邓哥很开心!")
                            } else {
                                console.log("邓哥表示生无可恋!!");
                            }
                        })
                    }
                })
            }
        })

回调地狱例子3:

 //获取李华所在班级的老师的信息 (数据在本地)
        ajax({
            url: "./data/students.json?name=李华",
            success: function(data) {
                for (let i = 0; i < data.length; i++) {
                    if (data[i].name === "李华") {
                        const cid = data[i].classId;
                        ajax({
                            url: "./data/classes.json?id=" + cid,
                            success: function(data) {
                                for (let i = 0; i < data.length; i++) {
                                    if (data[i].id === cid) {
                                        const tid = data[i].teacherId;
                                        ajax({
                                            url: "./data/teachers.json?id=" + tid,
                                            success: function(data) {
                                                for (let i = 0; i < data.length; i++) {
                                                    if (data[i].id === tid) {
                                                        console.log(data[i]);
                                                    }
                                                }
                                            }
                                        })
                                        return;
                                    }
                                }
                            }
                        })
                        return;
                    }
                }
            }
        })

异步之间的联系例子:

 /*
    邓哥心中有二十个女神,他决定用更加高效的办法
    他同时给二十个女神表白,如果有女神同意,就拒绝其他的女神
    并且,当所有的女神回复完成后,他要把所有的回复都记录到日志进行分析
    用代码模拟上面的场景
        */
        function biaobai(god, callback) {
            console.log(`邓哥向女神【${god}】发出了表白短信`);
            setTimeout(() => {
                if (Math.random() < 0.05) {
                    //女神同意拉
                    callback(true);
                } else {
                    callback(false);
                }
            }, Math.floor(Math.random() * (3000 - 1000) + 1000));
        }
        let agreeGod = null; //同意邓哥的第一个女神
        const results = []; //用于记录回复结果的数组
        for (let i = 1; i <= 20; i++) {
            biaobai(`女神${i}`, result => {
                results.push(result);

                if (result) {
                    console.log(`女神${i}同意了`)
                    if (agreeGod) {
                        console.log(`邓哥回复女神${i}: 不好意思,刚才朋友用我手机,乱发的`)
                    } else {
                        agreeGod = `女神${i}`;
                        console.log(`邓哥终于找到了真爱`);
                    }
                } else {
                    console.log(`女神${i}拒绝了`)
                }
                
                if (results.length === 20) {
                    console.log("日志记录", results)
                }
            })
        }
8-2. 异步处理的通用模型

ES官方参考了大量的异步场景,总结出了一套异步的通用模型,该模型可以覆盖几乎所有的异步场景,甚至是同步场景。

值得注意的是,为了兼容旧系统,ES6 并不打算抛弃掉过去的做法,只是基于该模型推出一个全新的 API,使用该API,会让异步处理更加的简洁优雅。

理解该 API,最重要的,是理解它的异步模型

  1. ES6 将某一件可能发生异步操作的事情,分为两个阶段:unsettledsettled

在这里插入图片描述

  • unsettled: 未决阶段,表示事情还在进行前期的处理,并没有发生通向结果的那件事
  • settled:已决阶段,事情已经有了一个结果,不管这个结果是好是坏,整件事情无法逆转

事情总是从 未决阶段 逐步发展到 已决阶段的。并且,未决阶段拥有控制何时通向已决阶段的能力。

  1. ES6将事情划分为三种状态: pending、resolved、rejected
  • pending: 挂起,处于未决阶段,则表示这件事情还在挂起(最终的结果还没出来)
  • resolved:已处理,已决阶段的一种状态,表示整件事情已经出现结果,并是一个可以按照正常逻辑进行下去的结果
  • rejected:已拒绝,已决阶段的一种状态,表示整件事情已经出现结果,并是一个无法按照正常逻辑进行下去的结果,通常用于表示有一个错误

既然未决阶段有权力决定事情的走向,因此,未决阶段可以决定事情最终的状态!

我们将 把事情变为resolved状态的过程叫做:resolve,推向该状态时,可能会传递一些数据

我们将 把事情变为rejected状态的过程叫做:reject,推向该状态时,同样可能会传递一些数据,通常为错误信息

始终记住,无论是阶段,还是状态,是不可逆的!

在这里插入图片描述
3. 当事情达到已决阶段后,通常需要进行后续处理,不同的已决状态,决定了不同的后续处理。

  • resolved状态:这是一个正常的已决状态,后续处理表示为 thenable
  • rejected状态:这是一个非正常的已决状态,后续处理表示为 catchable

后续处理可能有多个,因此会形成作业队列,这些后续处理会按照顺序,当状态到达后依次执行

在这里插入图片描述
4. 整件事称之为Promise

在这里插入图片描述

理解上面的概念,对学习Promise至关重要!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值