异步处理
事件循环 eventLoop
JS 运行的环境称之为宿主环境。JS语言不只运行在浏览器
执行栈:call stack(一个数据结构),用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。
JS 引擎永远执行的是执行栈的最顶部。
异步函数:某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数称之为异步函数。比如事件处理函数。异步函数的执行时机,会被宿主环境控制。
浏览器宿主环境中包含 5 个线程:
- JS 引擎:负责执行执行栈的最顶部代码
- GUI 线程:负责渲染页面
- 事件监听线程:负责监听各种事件
- 计时线程:负责计时
- 网络线程:负责网络通信
当上面的线程发生了某些事请,如果该线程发现,这件事情有处理程序,它会将该处理程序加入一个叫做事件队列的内存。当 JS 引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。
JS 引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。
JavaScript 运行机制详解:再谈Event Loop
事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:
- 宏任务(队列):macroTask,计时器结束的回调、事件回调、http 回调等等绝大部分异步函数进入宏队列
- 微任务(队列):MutationObserver,Promise 产生的回调进入微队列——vip
MutationObserver 用于监听某个 DOM 对象的变化
当执行栈清空时,JS 引擎首先会将微任务中的所有任务依次执行结束,如果没有微任务,则执行宏任务。
看下面一串代码
1<script>
2 let count = 1;
3 const ul = document.getElementById("container");
4 document.getElementById("btn").onclick = function A() {
5 var li = document.createElement("li")
6 li.innerText = count++;
7 ul.appendChild(li);
8 console.log("添加了一个li")
9 }
10
11 //监听ul
12 const observer = new MutationObserver(function B() {
13 //当监听的dom元素发生变化时运行的回调函数
14 console.log("ul元素发生了变化")
15 })
16 //监听ul
17 observer.observe(ul, {
18 attributes: true, //监听属性的变化
19 childList: true, //监听子元素的变化
20 subtree: true //监听子树的变化
21 })
22</script>
为什么先执行宏队列了?
点击按钮的时候还没有往ul里面加li,所以微队列也不执行
宏队列演示
1<ul id="container">
2
3</ul>
4
5<button id="btn">点击</button>
6<script>
7 let count = 1;
8 const ul = document.getElementById("container");
9 document.getElementById("btn").onclick = function A() {
10 setTimeout(function C() {
11 console.log("添加了一个li")
12 }, 0);
13 var li = document.createElement("li")
14 li.innerText = count++;
15 ul.appendChild(li);
16 }
17
18 //监听ul
19 const observer = new MutationObserver(function B() {
20 //当监听的dom元素发生变化时运行的回调函数
21 console.log("ul元素发生了变化")
22 })
23 //监听ul
24 observer.observe(ul, {
25 attributes: true, //监听属性的变化
26 childList: true, //监听子元素的变化
27 subtree: true //监听子树的变化
28 })
29 //取消监听
30 // observer.disconnect();
31</script>
事件和回调函数缺陷
我们习惯于使用传统的回调或事件处理来解决异步问题
事件:某个对象的属性是一个函数,当发生某一件事时,运行该函数
1dom.onclick = function () {};
回调:运行某个函数以实现某个功能的时候,传入一个函数作为参数,当发生某件事的时候,会运行该函数。
1dom.addEventListener("click", function () {});
本质上,事件和回调并没有本质的区别,只是把函数放置的位置不同而已。一直以来,该模式都运作良好。直到前端工程越来越复杂...
目前,该模式主要面临以下两个问题:
-
回调地狱:某个异步操作需要等待之前的异步操作完成,无论用回调还是事件,都会陷入不断的嵌套
-
异步之间的联系:某个异步操作要等待多个异步操作的结果,对这种联系的处理,会让代码的复杂度剧增
1<p>
2 <button id="btn1">按钮1:给按钮2注册点击事件</button>
3 <button id="btn2">按钮2:给按钮3注册点击事件</button>
4 <button id="btn3">按钮3:点击后弹出hello</button>
5</p>
6<script>
7 const btn1 = document.getElementById("btn1"),
8 btn2 = document.getElementById("btn2"),
9 btn3 = document.getElementById("btn3");
10 btn1.addEventListener("click", function() {
11 //按钮1的其他事情
12 btn2.addEventListener("click", function() {
13 //按钮2的其他事情
14 btn3.addEventListener("click", function() {
15 alert("hello");
16 })
17 })
18 })
19</script>
异步处理的通用模型
ES 官方参考了大量的异步场景,总结出了一套异步的通用模型,该模型可以覆盖几乎所有的异步场景,甚至是同步场景。
值得注意的是,为了兼容旧系统,ES6 并不打算抛弃掉过去的做法,只是基于该模型推出一个全新的 API,使用该 API,会让异步处理更加的简洁优雅。
理解该 API,最重要的,是理解它的异步模型
-
ES6 将某一件可能发生异步操作的事情,分为两个阶段:unsettled 和 settled
- unsettled: 未决阶段,表示事情还在进行前期的处理,并没有发生通向结果的那件事
- settled:已决阶段,事情已经有了一个结果,不管这个结果是好是坏,整件事情无法逆转
事情总是从 未决阶段 逐步发展到 已决阶段的。并且,未决阶段拥有控制何时通向已决阶段的能力。
- ES6 将事情划分为三种状态: pending、resolved、rejected
- pending: 挂起,处于未决阶段,则表示这件事情还在挂起(最终的结果还没出来)
- resolved:已处理,已决阶段的一种状态,表示整件事情已经出现结果,并是一个可以按照正常逻辑进行下去的结果
- rejected:已拒绝,已决阶段的一种状态,表示整件事情已经出现结果,并是一个无法按照正常逻辑进行下去的结果,通常用于表示有一个错误
既然未决阶段有权力决定事情的走向,因此,未决阶段可以决定事情最终的状态!
我们将 把事情变为 resolved 状态的过程叫做:resolve,推向该状态时,可能会传递一些数据
我们将 把事情变为 rejected 状态的过程叫做:reject,推向该状态时,同样可能会传递一些数据,通常为错误信息
始终记住,无论是阶段,还是状态,是不可逆的!
- 当事情达到已决阶段后,通常需要进行后续处理,不同的已决状态,决定了不同的后续处理。
- resolved 状态:这是一个正常的已决状态,后续处理表示为 thenable
- rejected 状态:这是一个非正常的已决状态,后续处理表示为 catchable
后续处理可能有多个,因此会形成作业队列,这些后续处理会按照顺序,当状态到达后依次执行
-
整件事称之为 Promise
理解上面的概念,对学习 Promise 至关重要!
Promise A+
Promise是一套专门处理异步场景的规范,它能有效的避免回调地狱的产生,使异步代码更加清晰、简洁、统一
这套规范最早诞生于前端社区,规范名称为Promise A+
该规范出现后,立即得到了很多开发者的响应
Promise A+ 规定:
- 所有的异步场景,都可以看作是一个异步任务,每个异步任务,在JS中应该表现为一个对象,该对象称之为Promise对象,也叫做任务对象

- 每个任务对象,都应该有两个阶段、三个状态

根据常理,它们之间存在以下逻辑:
- 任务总是从未决阶段变到已决阶段,无法逆行
- 任务总是从挂起状态变到完成或失败状态,无法逆行
- 时间不能倒流,历史不可改写,任务一旦完成或失败,状态就固定下来,永远无法改变
挂起->完成,称之为resolve;挂起->失败称之为reject。任务完成时,可能有一个相关数据;任务失败时,可能有一个失败原因。

- 可以针对任务进行后续处理,针对完成状态的后续处理称之为onFulfilled,针对失败的后续处理称之为onRejected

ES6提供了一套API,实现了Promise A+规范
Promise的基本使用
1const pro = new Promise((resolve, reject)=>{
2 // 未决阶段的处理
3 // 通过调用resolve函数将Promise推向已决阶段的resolved状态
4 // 通过调用reject函数将Promise推向已决阶段的rejected状态
5 // resolve和reject均可以传递最多一个参数,表示推向状态的数据
6 // 立即执行
7})
8
9pro.then(data=>{
10 //这是thenable函数,如果当前的Promise已经是resolved状态,该函数会立即执行
11 //如果当前是未决阶段,则会加入到作业队列,等待到达resolved状态后执行
12 //data为状态数据
13}, err=>{
14 //这是catchable函数,如果当前的Promise已经是rejected状态,该函数会立即执行
15 //如果当前是未决阶段,则会加入到作业队列,等待到达rejected状态后执行
16 //err为状态数据
17})
细节
- 未决阶段的处理函数是同步的,会立即执行
- thenable和catchable函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,并且,加入的队列是微队列
- pro.then可以只添加thenable函数,pro.catch可以单独添加catchable函数
- 在未决阶段的处理函数中,如果发生未捕获的错误,会将状态推向rejected,并会被catchable捕获
- 一旦状态推向了已决阶段,无法再对状态做任何更改
- Promise并没有消除回调,只是让回调变得可控
创建 Promise
1const pro = new Promise((resolve, reject) => {
2 setTimeout(() => {
3 if (Math.random() < 0.1) {
4 resolve(true);
5 } else {
6 resolve(false);
7 }
8 }, 3000);
9})
封装Ajax
1// 辅助函数,把传进来的对象拼接成url的字符串
2function toData(obj) {
3 if (obj === null) {
4 return obj;
5 }
6 let arr = [];
7 for (let i in obj) {
8 let str = i + "=" + obj[i];
9 arr.push(str);
10 }
11 return arr.join("&");
12}
13// 封装Ajax
14function ajax(obj) {
15 return new Promise((resolve, reject) => {
16 //指定提交方式的默认值
17 obj.type = obj.type || "get";
18 //设置是否异步,默认为true(异步)
19 obj.async = obj.async || true;
20 //设置数据的默认值
21 obj.data = obj.data || null;
22 // 根据不同的浏览器创建XHR对象
23 let xhr = null;
24 if (window.XMLHttpRequest) {
25 // 非IE浏览器
26 xhr = new XMLHttpRequest();
27 } else {
28 // IE浏览器
29 xhr = new ActiveXObject("Microsoft.XMLHTTP");
30 }
31 // 区分get和post,发送HTTP请求
32 if (obj.type === "post") {
33 xhr.open(obj.type, obj.url, obj.async);
34 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
35 let data = toData(obj.data);
36 xhr.send(data);
37 } else {
38 let url = obj.url + "?" + toData(obj.data);
39 xhr.open(obj.type, url, obj.async);
40 xhr.send();
41 }
42 // 接收返回过来的数据
43 xhr.onreadystatechange = function () {
44 if (xhr.readyState === 4) {
45 if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
46 resolve(JSON.parse(xhr.responseText))
47 } else {
48 reject(xhr.status)
49 }
50 }
51 }
52 })
53}
54
55const pro = Promise((resolve, reject) => {
56 ajax({
57 url: "./data/students.json?name=李华",
58 // success: function(data) {
59 // }
60 // 速写
61 success(data) {
62 resolve(data);
63 },
64 error(err) {
65 reject(error);
66 }
67 })
68})
如果当前的Promise已经是resolved状态,该函数会立即执行
1const pro = new Promise((resolve, reject) => {
2 console.log('未决阶段')
3 resolve(123);
4})
5pro.then(data => {
6 //pro的状态是resloved
7 console.log(data);// 123
8})
如果当前是未决阶段,则会加入到作业队列,等待到达resolved状态后执行
1const pro = new Promise((resolve, reject) => {
2 console.log('未决阶段')
3 setTimeout(() => {
4 resolve(123);
5 }, 3000)
6})
7pro.then(data => {
8 //pro的状态是pending
9 console.log(data);
10})
11// 可以注册多个then用来表示已决做什么
catchable
1const pro = new Promise((resolve, reject) => {
2 console.log('未决阶段')
3 setTimeout(() => {
4 if (Math.random() < 0.5) {
5 resolve(123)
6 } else {
7 reject(new Error('sanjs'));
8 }
9 }, 3000)
10})
11pro.then(data => {
12 console.log(data);
13}, err => {
14 console.log(err);
15})
封装初级演示1
1function biaobai(god) {
2 return new Promise((resolve, reject) => {
3 console.log(`向${god}发出了请求`);
4 setTimeout(() => {
5 if (Math.random() < 0.1) {
6 resolve(true)// 一定resolve成功
7 } else {
8 //resolve
9 resolve(false);// 一定resolve成功
10 }
11 }, 3000);
12 })
13}
14//一定成功,失败指的是短信发不出去
15const pro = biaobai("女神1").then(result => {
16 console.log(result);
17})
演示2
1<script>
2 // const pro = new Promise((resolve, reject) => {
3 // console.log("未决阶段")
4 // resolve(123);
5 // })
6 // pro.then(data => {
7 // // pro的状态是resolved
8 // console.log(data);
9 // })
10
11 const pro = new Promise((resolve, reject) => {
12 console.log("未决阶段")
13 setTimeout(() => {
14 resolve(123);
15 }, 3000);
16 })
17 pro.then(data => {
18 // pro的状态是pending
19 console.log(data);
20 })
21 pro.then(data => {
22 // pro的状态是pending
23 console.log(data);
24 })
25 pro.then(data => {
26 // pro的状态是pending
27 console.log(data);
28 })
29</script>
演示4
1<script>
2 const pro = new Promise((resolve, reject) => {
3 console.log("未决阶段")
4 setTimeout(() => {
5 if (Math.random < 0.5) {
6 resolve(123)
7 } else {
8 reject(new Error("asdfasdf"));
9 }
10 }, 3000);
11 })
12 pro.then(data => {
13 console.log(data);
14 }, err => {
15 console.log(err)
16 })
17</script>
演示5
1<script>
2 const pro = new Promise((resolve, reject) => {
3 console.log("a")
4 resolve(1);
5 setTimeout(() => {
6 console.log("b")
7 }, 0);
8 })
9 //pro: resolved
10 pro.then(data => {
11 console.log(data)
12 })
13 pro.catch(err => {
14 console.log(err)
15 })
16 console.log("c")
17</script>
在未决阶段的处理函数中,如果发生未捕获的错误,会将状态推向rejected,并会被catchable捕获
1<script>
2 const pro = new Promise((resolve, reject) => {
3 throw new Error("123"); // 导致pro变成rejected
4 })
5 pro.then(data => {
6 console.log(data)
7 })
8 pro.catch(err => {
9 console.log(err)
10 })
11</script>
一旦状态推向了已决阶段,无法再对状态做任何更改。(未捕获的错误)
如果已经捕获了错误
1const pro = new Promise((resolve, reject) => {
2 try {
3 throw new Error("abc");
4 } catch {
5 }
6 resolve(1); //错误被捕获,有效了
7 reject(2); //无效
8 resolve(3); //无效
9 reject(4); //无效
10})
11pro.then(data => {
12 console.log(data)
13})
14pro.catch(err => {
15 console.log(err)
16}
未决阶段的处理函数是同步的,会立即执行thenable和catchable函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,并且,加入的队列是微队列
注意:thenable和catchable函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,并且,加入的队列是微队列
Promise的串联
当后续的Promise需要用到之前的Promise的处理结果时,需要Promise的串联
Promise对象中,无论是then方法还是catch方法,它们都具有返回值,返回的是一个全新的Promise对象,它的状态满足下面的规则:
- 如果当前的Promise是未决的,得到的新的Promise是挂起状态
- 如果当前的Promise是已决的,会运行响应的后续处理函数,并将后续处理函数的结果(返回值)作为resolved状态数据,应用到新的Promise中;如果后续处理函数发生错误,则把返回值作为rejected状态数据,应用到新的Promise中。
1const pro = ajax({
2 url: "./data/students.json"
3})
4const pro2 = pro.then(resp => {// 这里面要运行完pro2才已决
5 // throw new Error('错误')// reject
6 for (let i = 0; i < resp.length; i++) {
7 if (resp[i].name === "李华") {
8 const cid = resp[i].classId;
9 }
10 }
11})
12console.log(pro2)
面试题:后续的Promise一定会等到前面的Promise有了后续处理结果后,才会变成已决状态
1const pro1 = new Promise((resolve, reject) => {
2 resolve(1);
3})
4console.log(pro1);//fulfilled
5const pro2 = pro1.then(result => result * 2);
6// pro2是一个Promise对象
7console.log(pro2)//pending
8// 因为then是异步的,运行到打印pro2的时候函数还没调用(同步代码)
面试题
1const pro1 = new Promise((resolve, reject) => {
2 resolve(1);
3})
4const pro2 = pro1.then(result => result * 2);//这里变成状态数据:2,pro2变成了已决
5pro2.then(result => console.log(result), err => console.log(err))
面试题
1const pro1 = new Promise((resolve, reject) => {
2 throw 1;// 推向rejected,导致pro2的err运行
3})
4const pro2 = pro1.then(result => {
5 return result * 2;
6}, err => err * 3);// 3, 此时pro2已决了
7pro2.then(result => console.log(result * 2), err => console.log(err * 3))//这里主要看上述处理有没有错误,pro2没有错误,执行result
8结果:6
变式
1const pro1 = new Promise((resolve, reject) => {
2 throw 1;
3})
4const pro2 = pro1.then(result => {
5 return result * 2;
6}, err => {
7 throw err;
8});
9pro2.then(result => console.log(result * 2), err => console.log(err * 3))//3
思考:then函数返回的Promise对象一开始一定是挂起状态:因为then的后序处理是异步的
1const pro1 = new Promise((resolve, reject) => {
2 throw 1;
3})
4const pro2 = pro1.then(result => {// 这里是第一个调用then的,只看这里的处理函数
5 return result * 2;
6}, err => {
7 return err * 3;//3
8});
9const pro3 = pro1.catch(err => {// 这里得到的是一个新的Promise
10 throw err * 2;//1*2
11})
12pro2.then(result => console.log(result * 2), err => console.log(err * 3)) // 输出6,调用的是result
13pro3.then(result => console.log(result * 2), err => console.log(err * 3)) // 输出6,调用的是err
如果前面的Promise的后续处理,返回的是一个Promise,则返回的新的Promise状态和后续处理返回的Promise状态保持一致。
1const pro1 = new Promise((resolve, reject) => {
2 resolve(1);
3})
4const pro2 = new Promise((resolve, reject) => {
5 resolve(2)
6})
7const pro3 = pro1.then(result => {
8 return pro2;
9}).then(result => {
10 console.log(result)// 2
11})
变式
1const pro1 = new Promise((resolve, reject) => {
2 resolve(1);
3})
4const pro2 = new Promise((resolve, reject) => {
5 setTimeout(() => {
6 resolve(2)
7 }, 3000)
8})
9const pro3 = pro1.then(result => {
10 console.log("结果出来了,得到的是一个Promise")//一开始Pro1已决了,所以这个一开始就执行
11 return pro2;// Pro3要跟pro2状态保持一致,pro2还没运行出来,pro3得等着
12}).then(result => {
13 console.log(result)// 2;所以这里要等3s后才输出
14})
简化为链式编程:便于阅读
1pro1.then(result => {
2 console.log("结果出来了,得到的是一个Promise")
3 return pro2;
4}).then(result => {
5 console.log(result)// 这只是打印结果,对后续有影响的是函数的返回值
6 // 不写,默认return undefined
7}).then(result => {
8 console.log(result)// undefined
9})
解决实战问题
1//获取李华所在班级的老师的信息
2//1. 获取李华的班级id Promise
3//2. 根据班级id获取李华所在班级的老师id Promise
4//3. 根据老师的id查询老师信息 Promise
5const pro = ajax({
6 url: "./data/students.json"
7})
8pro.then(resp => {
9 for (let i = 0; i < resp.length; i++) {
10 if (resp[i].name === "李华") {
11 return resp[i].classId; //班级id
12 }
13 }
14}).then(cid => {
15 return ajax({
16 url: "./data/classes.json?cid=" + cid
17 }).then(cls => {
18 for (let i = 0; i < cls.length; i++) {
19 if (cls[i].id === cid) {
20 return cls[i].teacherId;
21 }
22 }
23 })
24}).then(tid => {
25 return ajax({
26 url: "./data/teachers.json"
27 }).then(ts => {
28 for (let i = 0; i < ts.length; i++) {
29 if (ts[i].id === tid) {
30 return ts[i];
31 }
32 }
33 })
34}).then(teacher => {
35 console.log(teacher);
36})
实战案例
1function biaobai(god) {
2 return new Promise(resolve => {
3 console.log(`向${god}发出了请求`);
4 setTimeout(() => {
5 if (Math.random() < 0.3) {
6 resolve(true)
7 } else {
8 resolve(false);
9 }
10 }, 500);
11 })
12}
13biaobai("女神1").then(resp => {
14 if (resp) {
15 console.log("女神1同意了")
16 return;
17 } else {
18 return biaobai("女神2");
19 }
20}).then(resp => {
21 if (resp === undefined) {
22 return;
23 } else if (resp) {
24 console.log("女神2同意了")
25 return;
26 } else {
27 return biaobai("女神3");
28 }
29}).then(resp => {
30 if (resp === undefined) {
31 return;
32 } else if (resp) {
33 console.log("女神3同意了")
34 } else {
35 console.log("都被拒绝了!");
36 }
37})
38// 切记:上一个处理的结果就是下一个处理的状态数据
优化:增加了初始化
1const gods = ["女神1", "女神2", "女神3", "女神4", "女神5"];
2let pro;
3for (let i = 0; i < gods.length; i++) {
4 if (i === 0) {
5 pro = biaobai(gods[i]);
6 }
7 pro = pro.then(resp => {
8 if (resp === undefined) {
9 return;
10 } else if (resp) {
11 console.log(`${gods[i]}同意了`)
12 return;
13 } else {
14 console.log(`${gods[i]}拒绝了`)
15 if (i < gods.length - 1) {
16 return biaobai(gods[i + 1]);
17 }
18 }
19 })
20}
Promise的其他API
原型成员 (实例成员)
- then:注册一个后续处理函数,当Promise为resolved状态时运行该函数
- catch:注册一个后续处理函数,当Promise为rejected状态时运行该函数
- finally:[ES2018]注册一个后续处理函数(无参),当Promise为已决时运行该函数
一个Promise不可能then,catch都执行
1const pro = new Promise((resolve, reject) => {
2 reject(1);
3})
4pro.finally(() => console.log("finally1"))
5pro.finally(() => console.log("finally2"))
6pro.then(resp => console.log("then1", resp * 1));
7pro.then(resp => console.log("then2", resp * 2));
8pro.catch(resp => console.log("catch1", resp * 1));
9pro.catch(resp => console.log("catch2", resp * 2));
构造函数成员 (静态成员)
- resolve(数据):该方法返回一个resolved状态的Promise,传递的数据作为状态数据
1const pro = new Promise((resolve, reject) => {
2 resolve(1);
3})
4// 等效于:
5const pro = Promise.resolve(1);
特殊情况:如果传递的数据是Promise,则直接返回传递的Promise对象
1const p = new Promise((resolve, reject) => {
2 resolve(3);
3})
4// const pro = Promise.resolve(p);
5//等效于
6const pro = p;
7console.log(pro === p)//true
- reject(数据):该方法返回一个rejected状态的Promise,传递的数据作为状态数据
1const pro = new Promise((resolve, reject) => {
2 reject(1);
3})
4// 等效于:
5const pro = Promise.reject(1);
- all(iterable):这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。
1function getRandom(min, max) {
2 return Math.floor(Math.random() * (max - min)) + min;
3}
4const proms = [];
5for (let i = 0; i < 10; i++) {
6 proms.push(new Promise((resolve, reject) => {
7 setTimeout(() => {
8 if (Math.random() < 0.5) {
9 console.log(i, "完成");
10 resolve(i);
11 } else {
12 console.log(i, "失败")
13 reject(i);
14 }
15 }, getRandom(1000, 5000));
16 }))
17}
18//等到所有的promise变成resolved状态后输出: 全部完成
19const pro = Promise.all(proms)// 把proms数组传进去,返回新的promise对象,必须等到所有promise都变成resolve后才变成resolve
20pro.then(datas => {
21 console.log("全部完成", datas);
22});
23pro.catch(err => {
24 console.log("有失败的", err);
25})
26console.log(proms);// 为什么这里直接输出promise数组?因为这是同步代码,同步创建的Promise,只是promise状态需要等待
- race(iterable):当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象
1function getRandom(min, max) {
2 return Math.floor(Math.random() * (max - min)) + min;
3}
4const proms = [];
5for (let i = 0; i < 10; i++) {
6 proms.push(new Promise((resolve, reject) => {
7 setTimeout(() => {
8 if (Math.random() < 0.5) {
9 console.log(i, "完成");
10 resolve(i);
11 } else {
12 console.log(i, "失败")
13 reject(i);
14 }
15 }, getRandom(1000, 5000));
16 }))
17}
18//等到所有的promise变成resolved状态后输出: 全部完成
19// const pro = Promise.all(proms)
20// pro.then(data => {
21// console.log("全部完成了", data);
22// })
23// pro.catch(err => {
24// console.log("有人失败了", err);
25// })
26const pro = Promise.race(proms)
27pro.then(data => {
28 console.log("有人完成了", data);
29})
30pro.catch(err => {
31 console.log("有人失败了", err);
32})
33console.log(proms);
应用
1function biaobai(god) {
2 return new Promise((resolve, reject) => {
3 setTimeout(() => {
4 if (Math.random() < 0.05) {
5 console.log(god, "同意")
6 resolve(true);
7 } else {
8 console.log(god, "拒绝")
9 resolve(false);
10 }
11 }, Math.floor(Math.random() * (3000 - 1000) + 1000));
12 })
13}
14const proms = [];
15let hasAgree = false; // 是否有女神同意
16for (let i = 1; i <= 20; i++) {
17 const pro = biaobai(`女神${i}`).then(resp => {
18 if (resp) {
19 if (hasAgree) {
20 console.log("发错了短信,很机智的拒绝了")
21 } else {
22 hasAgree = true;
23 console.log("很开心,终于成功了!");
24 }
25 }
26 return resp;
27 })
28 proms.push(pro);
29}
30Promise.all(proms).then(results => {
31 console.log("日志记录", results);
32})
1Promise.allSettled([
2 Promise.resolve(1),
3 Promise.resolve(2),
4 Promise.reject('error')
5]).then(result=>{
6 /*
7 result: [
8 {status: "fulfilled", value: 1},
9 {status: "fulfilled", value: 2},
10 {status: "rejected", reason: "error"}
11 ]
12 */
13})
新规范方法Promise.allSettled等待全部已决,不存在失败
1Promise.allSettled(proms)
2 .then((result) => {
3 console.log('Promise.all resolved', result)
4})
5 .catch((reason) => {
6 console.log("Promise.all rejected", reason);
7});
应用
1var proms = [// Promise数组
2 getTestPromise(true, 1),
3 getTestPromise(false, new Error("failed")),
4 getTestPromise(true, 3),
5 getTestPromise(false, new Error("failed")),
6];
7Promise.allSettled(proms)
8 .then((result) => {
9 var sum = result.reduce((a, b) => a + (b.value ?? 0), 0);// 这个0表示第一次的值
10 console.log(result)
11 console.log(sum)
12})
ES2021 将引入 Promise.any() 方法,一旦该方法从 promise 列表或数组中命中首个 resolve(成功) 的 promise ,就会短路并返回一个值。如果所有 promise 都被 reject ,该方法则将抛出一个聚合的错误信息 (在 Example 1b 里有所展示)。
其区别于 Promise.race() 之处在于,后者在某个 promise 率先 resolve 或 reject 后都会短路。
示例
1var proms = [
2 getTestPromise(true, 1),
3 getTestPromise(false, new Error('failed')),
4 getTestPromise(true, 3),
5 getTestPromise(false, new Error('failed')),
6];
7Promise.any(proms)
8 .then(datas => {
9 console.log('Promise.all resolved', datas)
10})
11 .catch(reason => {
12 console.log("Promise.all rejected", reason);
13});
一个成功,就返回成功的那个data
全部失败,就会返回一个对象,对象里面有一个数组,记录错误
async和await
async 和 await 是 ES2016 新增两个关键字,它们借鉴了 ES2015 中生成器在实际开发中的应用,目的是简化 Promise api 的使用,并非是替代 Promise。
async
目的是简化在函数的返回值中对Promise的创建
async 用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置,被修饰函数的返回结果一定是 Promise 对象。
存在setTimeout就不能用了
1async function test(){
2 console.log(1);
3 return 2;// 完成时候的数据
4}
5//等效于
6function test(){
7 return new Promise((resolve, reject)=>{
8 console.log(1);
9 resolve(2);
10 })
11}
如果里面返回Promise就会返回这个Promise对象
1async function test() {
2 return new Promise(resolve => {
3 resolve(1);
4 })
5}
await
await关键字必须出现在async函数中!!!!
await用在某个表达式之前,如果表达式是一个Promise,则得到的是thenable中的状态数据。
1async function test1(){
2 console.log(1);
3 return 2;
4}
5async function test2(){
6 const result = await test1();
7 console.log(result);
8}
9test2();
等效于
1function test1(){
2 return new Promise((resolve, reject)=>{
3 console.log(1);
4 resolve(2);
5 })
6}
7function test2(){
8 return new Promise((resolve, reject)=>{
9 test1().then(data => {
10 const result = data;
11 console.log(result);
12 resolve();
13 })
14 })
15}
16test2();
如果await的表达式不是Promise,则会将其使用Promise.resolve包装后按照规则运行
1async function test() {
2 const result = await 1;// 等同于 await Promise.resolve(1)
3 `await`也可以等待其他数据
4 console.log(result)
5}
6//等价于
7function test() {
8 return new Promise((resolve, reject) => {
9 Promise.resolve(1).then(data => {
10 const result = data;
11 console.log(result);
12 resolve();
13 })
14 })
15}
16test();
17console.log(123);
18// 先123 1 返回的是promise 不会阻塞
演示4
1async function getPromise() {
2 if (Math.random() < 0.5) {
3 return 1;
4 } else {
5 throw 2;
6 }
7}
8
9async function test() {
10 try {
11 const result = await getPromise();
12 console.log("正常状态", result)
13 } catch (err) {
14 console.log("错误状态", err);
15 }
16}
17
18test();
改造计时器函数
1function delay(duration) {
2 return new Promise((resolve, reject) => {
3 setTimeout(() => {
4 resolve();
5 }, duration);
6 })
7}
8
9async function biaobai(god) {
10 console.log(`向${god}发出了表白短信`);
11 await delay(500);
12 return Math.random() < 0.3;
13}
再谈异步
单线程的主要优势是不需要考虑线程调度,降低了程序的复杂性
但在单线程中如果要处理需要等待的任务时,就必须要考虑阻塞的问题。
考虑下面的伪代码:
1var dom = document.getElementById("name"); // 获取某个dom元素
2var name = syncConnect("http://server/getname"); // 以同步的方式向服务器获取名字
3dom.innerHTML = name;
4otherTask(); // 其他无关任务

因此,JS引入异步来处理该问题
1var dom = document.getElementById("name"); // 获取某个dom元素
2asyncConnect("http://server/getname", function callback(result){ //以异步的方式向服务器获取名字
3 dom.innerHTML = result;
4});
5otherTask(); // 其他无关任务

执行栈
要想执行必须有执行上下文
JS执行引擎只会执行栈顶端的东西
1function A() {
2 console.log("A");// 函数调用,新建log上下文,入栈,执行完出栈
3 B();// 建立B的上下文
4}
5function B() {
6 console.log("B");// log上下文
7}
8A();// 创建A的上下文,入栈。JS执行引擎只会执行栈顶端的东西,所以不执行下一句,而是执行A
9console.log("global");
10// 答案:A B global
演练场
怎样理解JS的异步?
答案:事件发生、等待网络通信完成、等待计时结束等等。如果在执行线程上去等待,就浪费线程的宝贵执行时间,阻塞后续操作。更可怕的是,由于浏览器GUI线程和JS执行线程是互斥的,这就导致浏览器界面会因为JS的等待处于卡死状态。因此,JS通过异步来解决这个问题,当需要等待的时候,通知宿主的其他线程去做处理,执行线程则继续后续执行。当其他线程完成处理后,会发出通知,此时执行线程转而去执行事先定义好的回调函数即可。异步的方式充分了解放了执行线程,让执行线程可以毫无阻塞的运行,也就避免了浏览器宿主因为等待操作完成出现的卡死现象。
考点:GUI线程怎么渲染的?
JS社区提出了Promise A+规范,希望把异步规范化,并消除回调地狱
再后来,ES6 官方标准中提出了 Promise API 来处理异步,它满足 Promise A+ 规范
由于异步处理变得标准了,就给ES官方提供了进一步改进的空间,于是在ES7中出现了新的语法async await,它更加完美的解决了异步处理问题

高仿setTimeout
1function delay(duration) {
2 return new Promise((resolve) => {
3 setTimeout(() => {
4 resolve();
5 }, duration)
6 });
7}
8async function test() {
9 console.log(1);
10 await delay(1000);
11 console.log(2);
12 await delay(1000);
13 console.log(3)
14}
15test()
高仿setInterval
1function delay(duration) {
2 return new Promise((resolve) => {
3 setTimeout(() => {
4 resolve();
5 }, duration)
6 });
7}
8async function test() {
9 var count = 0;
10 while (true) {
11 await delay(1000);
12 console.log(count++);
13 }
14}
15test()
如何理解JS的单线程?
我们之所以称JS为单线程的语言,是因为它的执行引擎只有一个线程,并且不会在执行期间开启新的线程。而并非浏览器是单线程的。
单线程的应用程序具有以下的特点:
-
易于学习和理解:所有代码都是按照顺序从上到下执行的
-
易于掌控程序:由于代码都按照顺序执行,不会出现中断,也没有共享资源的争夺问题,极大的降低了开发难度。
-
更加合理的利用计算机资源:创建新的线程和销毁线程都会耗费额外的CPU和内存资源,没有良好的线程设计,将导致程序运行效率低下。而单线程的应用不受此影响
说一下 JS 异步解决方案发展历程
优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队,等着,会拖延整个程序的执行。)
缺点:回调地狱,不能用try catch捕获错误,不能return
优点:解决了回调地狱的问题
缺点:无法取消Promise,错误需要通过回调函数来捕获
特点:可以控制函数的执行,可以配合co函数库使用
优点:代码清晰,不用像Promise写一大堆then链,处理了回调地狱的问题
async较Generator的优势:
✅ 内置执行器。Generator函数的执行必须依靠执行器,而 Aysnc 函数自带执行器,调用方式跟普通函数的调用一样
✅ 更好的语义。async和await相较于*和yield 更加语义化
✅ 更广的适用性。yield命令后面只能是Thunk函数或Promise对象,async函数的await后面可以是Promise也可以是原始类型的值
✅ 返回值是Promise。async函数返回的是Promise对象,比Generator函数返回的Iterator对象方便,可以直接使用then()方法进行调用
缺点: await将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用await会导致性能上的降低。有传染性
Promise缺点
答案:无法取消
axios请求也是Promise,怎么取消请求的cancalToken
1const a = await 1; // 有await和没有有啥不一样:await要等待状态。一定使得a=?异步执行
2console.log(999)
3// promise的await使代码具有传染性,不管await是什么,都会变成异步,好不好?
4// 关于API设计,想时异步时同步(stateState)还是永远异步(await)还是node的可以同步可以异步?
await后面接什么?
答案:Promise对象 await
catch,能不能模拟then效果
代码输出结果
下面的代码输出结果是 :
1setTimeout(() => {
2 console.log(1)
3}, 0);
4
5var pro = new Promise(resolve => {
6 console.log(2);
7 resolve(3);
8 console.log(4)
9 reject(5);
10 console.log(6)
11})
12
13pro.then(data => {
14 console.log(data)
15}, err => {
16 console.log(err)
17})
18console.log(7)
19// 24731
下面的代码输出结果是 :
1const promise1 = new Promise((resolve, reject) => {
2 setTimeout(() => {
3 resolve()
4 }, 1000)
5})
6const promise2 = promise1.then(() => {//等着1
7 throw new Error("error");
8})
9
10console.log('promise1', promise1)
11console.log('promise2', promise2)
12
13setTimeout(() => {
14 console.log('promise1', promise1)
15 console.log('promise2', promise2)
16}, 2000)
变式
1const promise1 = new Promise((resolve, reject) => {
2 setTimeout(() => {
3 resolve()
4 }, 1000)
5})
6const promise2 = promise1.catch(() => {//2取决于1的话,看处理没处理这个状态,处理了,看他处理过程,没有处理就漏下来
7 throw new Error("error");//此处成功了没有处理,状态和1一样。这里只处理了错误
8})
9
10console.log('promise1', promise1)
11console.log('promise2', promise2)
12
13setTimeout(() => {
14 console.log('promise1', promise1)
15 console.log('promise2', promise2) //fulfilled
16}, 2000)
变式
1const promise1 = new Promise((resolve, reject) => {
2 setTimeout(() => {
3 reject();//promise是rejected
4 }, 1000)
5})
6const promise2 = promise1.catch(() => {//处理了错误,处理的过程没报错
7 return 2;
8}).then(()=>{
9 //此处要执行
10})
11
12console.log('promise1', promise1)
13console.log('promise2', promise2)
14
15setTimeout(() => {
16 console.log('promise1', promise1) // rejected
17 console.log('promise2', promise2) // fulfiled
18}, 2000)
变式
1const promise1 = new Promise((resolve, reject) => {
2 // 这里要是报错,发生在同步代码里面,返回rejected。
3 setTimeout(() => {
4 throw 1;//但是此处跑错是在异步代码里面。执行的此处的时候,已经没有proimise环境了
5 }, 1000)
6})
7const promise2 = promise1.catch(() => {
8 return 2;
9})
10
11console.log('promise1', promise1)
12console.log('promise2', promise2)
13
14setTimeout(() => {
15 console.log('promise1', promise1) // pending
16 console.log('promise2', promise2) // pending
17}, 2000)
下面代码的运行结果是
1const promise = new Promise((resolve, reject) => {
2 resolve('success1')
3 reject('error')
4 resolve('success2')
5})
6
7promise
8 .then((res) => {
9 console.log('then: ', res)
10 })
11 .catch((err) => {
12 console.log('catch: ', err)
13 })
下面的代码输出结果是多少
1Promise.resolve(2)
2 .then((res) => {
3 console.log(res)
4 return 2
5 })//返回一个新Promsie,状态为成功
6 .catch((err) => {
7 return 3
8 })
9 .then((res) => {
10 console.log(res) //2
11 })
下面的代码输出结果是多少
1Promise.resolve()
2 .then(() => {
3 return new Error('error!!!')//这里是return 不是throw。没报错,只是返回了一个错误对象
4 })
5 .then((res) => {
6 console.log('then: ', res)
7 })
8 .catch((err) => {
9 console.log('catch: ', err)
10 })
下面的代码输出结果是多少
1Promise.resolve(1) // promise fullfilled 1
2 .then(2)//如果then里传递的不是函数,就当没执行就行
3 .then(Promise.resolve(3))//传递的是对象,then必须传函数,也是无效
4 .then(console.log)//函数 console.log(1)
下面代码的输出结果是什么
1const promise = new Promise((resolve, reject) => {
2 console.log(1);
3 resolve();
4 console.log(2);
5})
6
7promise.then(() => {
8 console.log(3);
9})
10
11console.log(4);
下面代码的输出结果是什么
1const promise = new Promise((resolve, reject) => {
2 console.log(1);
3 setTimeout(()=>{
4 console.log(2)
5 resolve();
6 console.log(3);
7 })
8})
9
10promise.then(() => {
11 console.log(4);
12})
13
14console.log(5);
下面代码的输出结果是什么
1const promise1 = new Promise((resolve, reject) => {
2 setTimeout(() => {
3 resolve()
4 }, 1000)
5})
6const promise2 = promise1.catch(() => {
7 return 2;
8})
9
10console.log('promise1', promise1)
11console.log('promise2', promise2)
12
13setTimeout(() => {
14 console.log('promise1', promise1)
15 console.log('promise2', promise2)
16}, 2000)
下面代码的输出结果是什么
1async function m(){
2 const n = await 1;
3 console.log(n);
4}
5
6m();
7console.log(2);
下面代码的输出结果是什么
1async function m(){
2 const n = await 1;
3 console.log(n);
4}
5
6(async ()=>{
7 await m();
8 console.log(2);
9})();
10
11console.log(3);
下面代码的输出结果是什么
1async function m1(){
2 return 1;
3}
4
5async function m2(){
6 const n = await m1();
7 console.log(n)
8 return 2;
9}
10
11async function m3(){
12 const n = m2();
13 console.log(n);
14 return 3;
15}
16
17m3().then(n=>{
18 console.log(n);
19});
20
21m3();
22
23console.log(4);
下面代码的输出结果是什么
1Promise.resolve(1)
2 .then(2)
3 .then(Promise.resolve(3))
4 .then(console.log)
下面代码的输出结果是什么
1var a;
2var b = new Promise((resolve, reject) => {
3 console.log('promise1');
4 setTimeout(()=>{
5 resolve();
6 }, 1000);
7}).then(() => {
8 console.log('promise2');
9}).then(() => {
10 console.log('promise3');
11}).then(() => {
12 console.log('promise4');
13});
14
15a = new Promise(async (resolve, reject) => {
16 console.log(a);
17 await b;
18 console.log(a);
19 console.log('after1');
20 await a
21 resolve(true);
22 console.log('after2');
23});
24
25console.log('end');
下面代码的输出结果是什么
1async function async1() {
2 console.log('async1 start');
3 await async2();
4 console.log('async1 end');
5}
6async function async2() {
7 console.log('async2');
8}
9
10console.log('script start');
11
12setTimeout(function() {
13 console.log('setTimeout');
14}, 0)
15
16async1();
17
18new Promise(function(resolve) {
19 console.log('promise1');
20 resolve();
21}).then(function() {
22 console.log('promise2');
23});
24console.log('script end');