本文章的目录结构
- Promise的含义
- 通过回调函数解决异步编程的方案
- 通过自定义事件解决异步编程
- 通过Promise的解决异步编程
- Promise的必备知识之三种状态
-
- 理解Promise构造函数的两种参数用法
-
- 理解pending(待定)、fulfilled(已成功)和rejected(已失败)三种状态
-
- 理解Promise.then()方法的三种返回值情况
- Promise的进阶知识之EventLoop
-
- 理解js世界里的EventLoop的代码执行规则顺序和过程
-
- 理解记忆宏任务、微任务、同步代码
-
- 理解async和awiat的使用和注意点
- Promise的进阶知识之Promise.resolve和Promise.reject
-
- 理解两种方法的基本使用方法
- Promise的进阶知识之all和race
-
- 理解all和race的使用和注意点
-
- Promise的进阶知识之then、catch、finally
-
- 理解then、catch、finally的使用和注意点
本文章的Promise的重点知识整理(先看正文再回看知识点)
- 只有异步操作的结果,可以决定当前Promise是哪一种状态(分别是pending(待定)、fulfilled(已成功)和rejected(已失败))。一旦状态改变,就不会再变,任何时候都可以得到这个结果。
- Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。而且Promise新建后就会立即执行。
- 不返回任何值的then方法会方法值穿透的问题。 then方法中不返回任何值但是Promise中有resolve()的执行逻辑的总结:不返回值的情况下会自动根据上一个Promise里的状态返还一个状态为fulfilled的promise对象,如果上一个Promise的PromiseResult为undefined,则这个通过不返回任何值的then方法返回的Promise对象的PromiseResult也是undefined。
- then方法中返回普通值(即除了不是Promise的值)的总结:会自动根据返回的普通值包装为一个状态为fulfilled和PromiseResult为该返回的普通值的Promise对象。
- then方法中返回一个fulfilled状态的Promise对象返回一个fulfilled状态的Promise对象和返回一个rejected状态的Promise对象的总结:会自动根据返回的这个Promise包装为下一个then链式调用的Promise对象
- 可以被添加进宏任务容器中的宏任务包括:页面的
<script></script>脚本、setTimeout、setInterval、setImmediate
- 可以被添加进微任务容器中的微任务包括:Promise.then()/catch()/finally()等、以Promise为基础开发的其他技术,例如fetch Api、async/await、await语句的下一行及之后的内容
- 可以视为同步代码立即执行的有:console.log()、new Promise()、紧跟await后面的语句里的内容例如 await async1()
- await等待的结果跟Promise里面的resolve()和reject()的调用时刻有关,等待两个中其中一个有反应后才会去执行下一个await语句。如果状态始终是pending,则会等待到海枯石烂。
- await只会等待一个async函数,不会也不能是多个。await只会等待当前一个async函数里面的这个异步。
- 正常情况下,async中的await命令是一个Promise对象,返回该对象的结果。但如果不是Promise对象的话,就会直接返回对应的值,相当于Promise.resolve()。
- 如果在async函数中抛出了错误(接收到一个状态为rejected的Promise或者throw new Error),则终止错误结果,不会继续向下执行。
- 紧跟着await后面的语句相当于放到了new Promise中,可以视为异步代码同步执行(可以立即执行),并且这个await后面的语句的Promise状态默认是resolve(pending->resolve)的,之后的语句可以理解为在状态为resolve之后(不考虑状态为rejected)放在Promise.then之内执行的。
- 有了all(),就可以并行的执行多个异步操作,并且在.all()后面的.then()中的回调中处理所有的返回数据
- 但是如果一组异步数据操作中有一个出现异常则都不会进入.then()的第一个回调函数参数中。这时候尽管.then()方法拿不到数据,但是每一个传入的异步数据都会执行。与此同时还可以通过.catch()函数来捕获到.all()里最先出现的那个异常。
- .race()方法会先拿到执行快的结果,不管是promise.race还是Promise.all都会执行函数体的内容(就算其中一个会抛出错误也不影响其他函数的执行并且第一个抛出错误的数据可以被catch方法捕获),区别是能否返回拿到一个有用的promise对象供后续使用。
- .race()方法只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
- catch不管被连接到哪里,都能捕获上层未捕捉过的错误。注意是未捕捉的错误,如果已经上头已经捕捉了错误,catch是不会起到作用的。
- .finally方法也是返回一个Promise,在它之前的链式调用的Promise无论结果状态为resolved还是rejected都会执行.finally()里面的回调函数。
- .finally()方法的回调函数不接收传入任何参数。它返回的始终是上一次的Promise对象值(但是如果在.finally()里抛出的是一个异常即throw new Error那么返回的是异常的Promise对象)。
Promise的含义
- Promise 是异步编程的一种解决方案,比传统的解决方案(回调函数和自定义监听发布事件)更加简洁和强大。
- 从语法上来将,Promise是一个对象,它可以将消息传递给未来需要调用的回调函数中,即未来的异步操作。
var p=new Promise(resolve=>{});
console.log(p)
- 我们通过打印出一个Promise对象来查看它的状态(这里是pending),然后再把目光放在观察其身上的静态属性和方法(这里勾出了几个常用的静态属性和方法)
- 如果直接开讲Promise的各种用法,大家可能不能更深刻的理解使用Promise的好处,于是这里我先带大家回顾之前解决异步编程的两种方案
通过回调函数解决异步编程的方案
现实需求:假设有张三、李四、王五三个人,它们分别干着三件不同的事情,正常来说,如果大家的活都差不多,那么项目交付顺序就是张三-李四-王五,但是这次李四这个人需要干的活比另外两个人要干的活要付出更多的时间,可是此时的总管依然希望项目的交付顺序是张三-李四-王五。
- 以代码的方式呈现为:
function zhangsan (){
console.log("做事情1");
}
function lisi (){
setTimeout(()=>{
console.log("做事情2");
},10000)
}
function wangwu (){
console.log("做事情3");
}
zhangsan();
lisi()
wangwu()
//此时的执行顺序为:
//做事情1
//做事情3
//做事情2
- 根据需求我需要让执行顺序符合我的预期即(做事情1-做事情2-做事情3),于是代码可以通过回调函数的方式改造为:
function zhangsan (){
console.log("做事情1");
}
function lisi (cb){
setTimeout(()=>{
console.log("做事情2");
cb && cb();
},1000)
}
function wangwu (){
console.log("做事情3");
}
zhangsan();
lisi(()=>{
wangwu();
});
- 通过传入回调函数并判断回调函数是否传入(传入即执行)的方式,让事件执行的顺序又符合了我们的需求。在这段代码中,我们并不能看出通过回调函数的方式解决异步编程的痛点,接下来我们通过一段代码来感受一下回调地狱。
现实需求:我们在页面定义一个离页面左侧0px的宽高为100px的绝对定位红色方块,我们想要先让它向右移动300px,之后在向下移动300px,之后在向左移动300px,最后再向上移动300px回到原点。(效果图如下)
- 该需求的实现逻辑如下:(重点关注函数调用的方式)
/*
ele 元素节点
target 目标位置
dis 移动方向
cb 移动结束后执行的回调函数
*/
function move(ele, target, dis, cb) {
//获取目标元素的left(以dis为left举例)属性值(取整去除单位)
let distance = parseInt(window.getComputedStyle(ele, null)[dis]);
//获取移动的速度(注意有正负之分)
let speed = 1 * (target - distance) / Math.abs((target - distance));
// console.log(target,'target')
// distance是从0到300的过程
// 1*(300-0)/Math.abs(300-0) //1
//1*(0-300)/Math.abs(0-300) //-1
// console.log(distance,'distance')
setTimeout(() => {
distance += speed;
if (target === distance) {
// console.log("运动完成");
cb && cb();
// console.log(distance,'distance')
// console.log(speed)
// console.log(target,'target')
} else {
ele.style[dis] = distance + "px";
move(ele, target, dis, cb);
}
})
}
let ele = document.querySelector(".box");
- 该函数调用的方式代码为:
move(ele, 300, "left", () => {
move(ele, 300, "top", () => {
move(ele, 0, "left", () => {
// console.log("运动完成");
move(ele, 0, "top",()=>{
console.log("运动完成");
})
})
})
})
通过自定义事件解决异步编程
EventTarget()构造方法将会创建一个新的EventTarget 对象实例。该构造方法可以用来实现自定义事件的监听和发布。- 该构造函数详情规范可参考developer.mozilla.org/zh-CN/docs/…
同一现实需求的第二种改造方式(通过自定义事件):我们在页面定义一个离页面左侧0px的宽高为100px的绝对定位红色方块,我们想要先让它向右移动300px,之后在向下移动300px,之后在向左移动300px,最后再向上移动300px回到原点。
//浏览器自带的自定义监听发布事件方法(实现原理类似发布订阅模式)
let eventObj = new EventTarget();
//区分每一次运动要监听的事件名称
let num = 1;
function move(ele, target, dis) {
let left = parseInt(window.getComputedStyle(ele, null)[dis]);
let speed = 1 * (target - left) / Math.abs((target - left));
setTimeout(() => {
left += speed;
if (target === left) {
// console.log("运动完成");
// cb && cb();
// 自定义发布事件
eventObj.dispatchEvent(new CustomEvent("myevent" + num));
num++;
} else {
ele.style[dis] = left + "px";
move(ele, target, dis);
}
})
}
let ele = document.querySelector(".box");
- 该函数调用的方式代码为:
//先去触发move()执行
//-当执行到 if (target === left)里面的语句时会触发订阅事件"myevent1"
move(ele, 300, "left");
eventObj.addEventListener("myevent1", () => {
console.log("第一个运动完成")
move(ele, 300, "top");
})
eventObj.addEventListener("myevent2", () => {
console.log("第二个运动完成")
move(ele, 0, "left")
})
eventObj.addEventListener("myevent3", () => {
console.log("第三个运动完成")
move(ele, 0, "top");
})
eventObj.addEventListener("myevent4", () => {
console.log("第四个运动完成")
})
通过Promise的解决异步编程
- 我们了解了通过回调函数和自定义事件解决异步编程的方法,接着我们来看下如何通过Promise来解决这个需求:(这里我先给出了Promise的解决方案,然后我们在来学习Promise的必备知识)
- 你也可以先去看下一节的必备知识在回头来看解决方案啾咪❤❤
function move(ele, target, dis) {
return new Promise(resolve => {
function fn() {
let left = parseInt(window.getComputedStyle(ele, null)[dis]);
let speed = 1 * (target - left) / Math.abs((target - left));
setTimeout(() => {
left += speed;
if (target === left) {
// console.log("运动完成");
// cb && cb();
//如果条件成立则执行回调函数
resolve();
} else {
ele.style[dis] = left + "px";
// 不断递归返回promise对象是错误的
// 重新包装一个fn()函数来封装逻辑
// 确保Promise对象只返回一个
// 调取then方法是基于一个promise对象
// move(ele, target, dis);
fn();
}
})
}
fn();
})
}
- 该函数调用的方式代码为:
let ele = document.querySelector(".box");
//链式操作;
move(ele, 300, "left").then(()=>{
console.log("运动完成1!");
return move(ele, 300, "top")
}).then(()=>{
console.log("运动完成2!");
return move(ele, 0, "left");
}).then(()=>{
console.log("运动完成3!");
return move(ele, 0, "top");
}).catch(err=>{
console.log(err);
})
Promise的必备知识之三种状态
- 我们书写Promise的时候一定要注意它的三种状态,分别是pending(待定)、fulfilled(已成功)和rejected(已失败)。
- 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。
我们可以通过以下的代码来观察其状态的变化(两个例子运用了定时器+Promise的方式直观的体现出状态的改变):
let p1 = new Promise((resolve,reject)=>{
// resolve("success");
// reject("err");
setTimeout(()=>{
resolve('隔了1秒后状态由pending到fulfilled')
})
// setTimeout(()=>{
// reject('隔了1秒后状态由pending到fulfilled')
// })
})
console.log(p1);
let p1 = new Promise((resolve,reject)=>{
// resolve("success");
// reject("err");
// setTimeout(()=>{
// resolve('隔了1秒后状态由pending到fulfilled')
// })
setTimeout(()=>{333
reject('隔了1秒后状态由pending到fulfilled')
})
})
console.log(p1);
- Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。而且Promise新建后就会立即执行。
- resolve函数的作用是,将Promise对象的状态从“pending”变为“fulfilled”,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
- reject函数的作用是,将Promise对象的状态从“pending”变为“rejected”,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
- Promise对象实例生成后,可以用then方法分别指定resolved状态和rejected状态的回调函数。但是通常我们使用catch方法来处理then方法中rejected状态的回调函数。
下面是一个使用Promise来加载图片的实例
function loadImg() {
return new Promise((resolve, reject) => {
let img = new Image();
img.src = "https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1089874897,1268118658&fm=26&gp=0.jpg";
// img.src = "https://tim"
img.onload = function () {
console.log("加载完成");
resolve(img);
}
img.onerror = function () {
// console.log("加载失败");
reject("加载失败");
}
})
}
loadImg().then(img=>{
document.body.appendChild(img);
}).catch(err=>{
console.log(err);
})
//是一个Promise对象状态由pending转为fulfilled
console.log(loadImg())
- Promise对象实例生成后,便可以使用then方法,我们需要除了要知道then方法的两个参数的作用(处理fulfilled状态的的回调函数和处理rejected状态的回调函数),还需要知道then方法的三种返回值。
- then方法的三种返还值分别为:一是不返回任何值,二是返回普通值(即除了不是Promise的值),三是返回一个Promise对象。
以下的代码会分别展示三种then的返回值:
- 第一种情况:then方法中不返回任何值且Promise中没有resolve()的执行逻辑
let p1=new Promise((resolve,reject)=>{
//resolve(1)
})
console.log(p1.then())
p1.then().then((resolve)=>{
console.log(resolve)
})
- 第一种情况:then方法中不返回任何值但是Promise中有resolve()的执行逻辑
let p1=new Promise((resolve,reject)=>{
resolve(1)
})
console.log(p1.then())
//不返回任何值的then方法会发生值穿透
//这里的表现为第二个then方法依然拿到了值1
p1.then().then((resolve)=>{
console.log(resolve)
})
注意点:不返回任何值的then方法会方法值穿透的问题。 第一种情况总结:不返回值的情况下会自动根据上一个Promise里的状态返还一个状态为fulfilled的promise对象,如果上一个Promise的PromiseResult为undefined,则这个通过不返回任何值的then方法返回的Promise对象的PromiseResult也是undefined。
- 第二种情况:then方法中返回普通值(即除了不是Promise的值)
let p1=new Promise((resolve,reject)=>{
resolve(1)
})
//以返回对象举例
let res = p1.then(()=>{
return {a:1,b:2};
})
//返回一个状态为fulfilled的Promise对象
console.log(res)
let p1=new Promise((resolve,reject)=>{
resolve(1)
})
let res = p1.then(()=>{
//以返回数组举例
return [1,2,3];
}).then(r=>{console.log(r)})
//返回一个状态为fulfilled的Promise对象
console.log(res)
第二种情况总结:会自动根据返回的普通值包装为一个状态为fulfilled和PromiseResult为该返回的普通值的Promise对象。
- 第三种情况:返回一个fulfilled状态的Promise对象
let p1=new Promise((resolve,reject)=>{
resolve('第一个then链式调用使用的是我')
})
p1.then((resolve)=>{
return new Promise((resolve)=>{
resolve('接下来的then链式调用使用的是我')
})
}).then(resolve=>{console.log(resolve)})
console.log(p1.then((resolve)=>{
return new Promise((resolve)=>{
resolve('接下来的then链式调用使用的是我')
})
}))
- 第三种情况:返回一个rejected状态的Promise对象
let p1=new Promise((resolve,reject)=>{
resolve('第一个then链式调用使用的是我')
})
p1.then((resolve)=>{
return new Promise((resolve,reject)=>{
// resolve('接下来的then链式调用使用的是我')
reject('ss')
})
}).then(resolve=>{console.log(resolve)}).catch(e=>{console.log(e)})
console.log(p1.then((resolve)=>{
return new Promise((resolve,reject)=>{
// resolve('接下来的then链式调用使用的是我')
reject('ss')
})
}))
第三种情况总结:会自动根据返回的这个Promise包装为下一个then链式调用的Promise对象
Promise的进阶知识之EventLoop
- 理解js世界里的EventLoop的代码执行规则顺序
js是单线程语言,如果事件队列只能一件一件按照顺序排队执行的话,无疑是不切实际的。为了能够快速的响应用户交互、脚本加载执行、网络请求等需求,我们需要一种规则来定义事件循环的执行顺序,这就是js从‘事件队列’中执行事件的一套循环规则。
- EventLoop的执行过程
-
- 一开始整个脚本
<script></script>的内容作为一个宏任务执行
- 一开始整个脚本
-
- 承接上方的执行过程中,我们得再次区分出哪一部分是同步代码,哪一部分是宏任务,哪一部分是微任务。整个
<script></script>脚本下的同步任务立即执行,微任务在宏任务之前执行。
- 承接上方的执行过程中,我们得再次区分出哪一部分是同步代码,哪一部分是宏任务,哪一部分是微任务。整个
-
- 再次判断脚本下的宏任务和微任务当中是否还有包含着宏任务和微任务和同步任务。
-
- 判断包含关系中,包含着的同步代码可以直接执行,判断为包含着宏任务将此宏任务打上记号(第xx个宏任务)并放入宏任务容器中,判断包含着微任务将此微任务打上记号(第xx个微任务)并放入微任务容器中。
-
- 牢记微任务在每一个宏任务之前执行,就算宏任务在微任务进微任务容器之前就已经在宏任务容器里了,该微任务也可以插队比宏任务先执行。
-
- 执行浏览器UI线程的渲染工作。
-
- 检查是否有Web Worker任务,有则执行。
-
- 执行完本轮的循环(
<script></script>)后,我们需要回到尚在各自容器中的宏任务和微任务,依次循环,直到这些宏任务和微任务队列为空。
- 执行完本轮的循环(
-
- 记住在链式调用中例如
.then().then().catch().finally()相当于微任务容器中包含着相应的微任务,不会一股脑的全部依次执行。要等第一个微任务then()方法和页面中其他的微任务执行后,才会去执行链式里面的下一个微任务(链式深处的微任务同理往下执行)。
- 记住在链式调用中例如
-
- 记住容器中的容器(如宏任务中包含着微任务,只有执行到该宏任务时才能判断里面的微任务是页面中第几个执行的微任务)只有在该大容器执行时才能把子容器给打上记号。
- 可以被添加进宏任务容器中的宏任务包括:
-
- 页面的
<script></script>脚本
- 页面的
-
setTimeout、setInterval、setImmediate
-
I/O、UI rendering
- 可以被添加进微任务容器中的微任务包括:
-
Promise.then()/catch()/finally()等
-
- 以
Promise为基础开发的其他技术,例如fetch Api、async/await
- 以
-
await语句的下一行及之后的内容
-
MutationObserver
- 可以视为同步代码立即执行的有:
-
console.log()
-
new Promise()
-
紧跟await后面的语句里的内容例如 await async1()
- 演示代码一:
function delay(time,fn){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
fn();
resolve('完成了')
},time)
})
}
async function fn(){
let val=await delay(1000,function(){
console.log(1)
})
let val1=await delay(1000,function(){
console.log(2)
})
console.log(val1)
let val2=await delay(1000,function(){
console.log(3)
})
}
fn()
- 执行顺序为:
1
2
完成了
3
- 演示代码二:
console.log("start");
//定义了一个待执行的函数
var prepare = () =>
new Promise((resolve, reject) => {
console.log('我会在函数调用的时候才立即执行');
resolve("改变了Promise状态");
});
console.log("midway");
//函数在这里才算是执行了才能执行new Promise()里的内容
prepare().then(res => {
console.log(res);
});
console.log("end");
- 执行的逻辑为:
1.先检索出
<script></script>脚本下的所有的同步代码并立即执行。这里的同步代码的顺序分别是console.log("start");和console.log("midway");和new Promise((resolve, reject) => {console.log('我会在函数调用的时候才立即执行');})和console.log("end");
2.再去检索出脚本下的微任务(不包括容器中的容器即宏任务中的微任务)并执行(微任务总是会在宏任务之前执行)。这里微任务的顺序分别是.then(res => {console.log(res);});
- 执行顺序为:
start
midway
我会在函数调用的时候才立即执行
end
改变了Promise状态
- 演示代码三:
let p1 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("1111");
console.log('宏任务下的同步代码1')
}, 2000)
})
}
let p2 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("222");
// reject("err")
console.log('宏任务下的同步代码2')
}, 1000)
})
}
let p3 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("333");
console.log('宏任务下的同步代码3')
}, 3000)
})
}
async function asyncFn() {
try {
//等待这个异步执行完成
let res1 = await p1();
//await下一行及之后的代码会在上一条语句执行后执行
console.log(res1);
let res2 = await p2();
//await下一行及之后的代码会在上一条语句执行后执行
console.log(res2);
let res3 = await p3();
//await下一行及之后的代码会在上一条语句执行后执行
console.log(res3);
console.log("所有异步执行完成");
} catch (e) {
console.log(e);
}
}
asyncFn();
//改造上面的代码为:
let arr = [p1,p2,p3];
async function fn() { //6秒钟
for(let i=0;i<arr.length;i++){
let res = await arr[i]();
console.log(res);
}
}
console.log( fn() );
- 执行的顺序是(这里执行了6秒,如果不适用异步编程async/await这里的执行时间是3秒):
宏任务下的同步代码1
1111
宏任务下的同步代码2
222
宏任务下的同步代码3
333
所有异步执行完成
- 注意点1.:await等待的结果跟Promise里面的resolve()和reject()的调用时刻有关,等待两个中其中一个有反应后才会去执行下一个await语句。如果状态始终是pending,则会等待到海枯石烂。
let p1 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("1111");
console.log('宏任务下的同步代码1')
}, 2000)
})
}
//注意看p2这里的状态始终会是pending
let p2 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("222");
// reject("err")
console.log('宏任务下的同步代码2')
}, 1000)
})
}
let p3 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("333");
console.log('宏任务下的同步代码3')
}, 3000)
})
}
async function asyncFn() {
try {
let res1 = await p1();
console.log(res1);
//卡在了这一步
let res2 = await p2();
//后续的逻辑消息了
console.log(res2);
let res3 = await p3();
console.log(res3);
console.log("所有异步执行完成");
} catch (e) {
console.log(e);
}
}
asyncFn();
执行顺序为:(后续的逻辑消息不见了)
宏任务下的同步代码1
1111
宏任务下的同步代码2
- 注意点2:await只会等待一个async函数,不会也不能是多个。await只会等待当前一个async函数里面的这个异步。
//按照谁最快谁执行(错误的用法:耗时3秒钟)
let arr=[p1,p2,p3];
//这里面其实需要有三个async函数
arr.forEach(async (item)=>{
let res=await item();
console.log(res)
})
- 注意点3:正常情况下,async中的await命令是一个Promise对象,返回该对象的结果。但如果不是Promise对象的话,就会直接返回对应的值,相当于Promise.resolve()。
let arr = [p1,p2,p3];
async function fn() { //6秒钟
for(let i=0;i<arr.length;i++){
let res = await arr[i]();
console.log(res);
}
return {a:1,b:2};
}
// fn();
console.log('1',fn().then(r=>{console.log(r,'看async函数里有没有返回值')}))
- 注意点四:如果在async函数中抛出了错误(接收到一个状态为rejected的Promise或者throw new Error),则终止错误结果,不会继续向下执行。
async function async1 () {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
//接收到了一个状态为rejected的Promise
reject('promise reject')
})
//这里下面的代码不会执行
console.log('这里的代码会执行吗?');
return '我在去返回值有用吗?'
}
console.log('srcipt start')
async1().then(res => {
console.log(res,'这里能接受到值吗?')
}).catch(e=>{console.log(e,'这里能接受到错误吗')})
new Promise(resolve => {
console.log('promise2')
setTimeout(() => {
console.log('timer')
})
})
async function async1 () {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
// reject('promise reject')
resolve('promise resolve')
//状态一旦改变在想去改变是不可能的
reject('promise reject')
})
console.log('这里的代码会执行吗?');
return '我在去返回值有用吗?'
}
console.log('srcipt start')
async1().then(res => {
console.log(res,'这里能接受到值吗?')
}).catch(e=>{console.log(e,'这里能接受到错误吗')})
new Promise(resolve => {
console.log('promise2')
setTimeout(() => {
console.log('timer')
})
})
- 注意点五:紧跟着await后面的语句相当于放到了new Promise中,可以视为异步代码同步执行(可以立即执行),并且这个await后面的语句的Promise状态默认是resolve(pending->resolve)的,之后的语句可以理解为在状态为resolve之后(不考虑状态为rejected)放在Promise.then之内执行的。
- 演示代码三:
//遇到的第一个微任务
Promise.resolve().then(function(){
console.log('在Promise.then中的微任务1');
});
//宏任务一:注意这里的时间是0
setTimeout(function(){
console.log('宏任务中的同步代码1')
Promise.resolve().then(()=>{
console.log('宏任务中的微任务3')
})
console.log('宏任务中的同步代码2')
},0);
//new Promise中的代码会立即执行可以理解为同步代码
new Promise((resolve)=>{
console.log('我是new Promise下会立即执行的同步代码')
resolve('传下去微任务2的代码')
}).then(resolve=>{
console.log('前面')
console.log(resolve)
console.log('后面')
});
//宏任务二:注意这里的时间是0
setTimeout(function() {
console.log('我是第二个宏任务中的同步代码')
}, 0);
console.log('脚本下的同步代码')
- 检索图为:
- 执行逻辑为:
1.先检索出
<script></script>脚本下的所有的同步代码并立即执行。这里的同步代码的顺序分别是console.log('我是new Promise下会立即执行的同步代码')和console.log('脚本下的同步代码')
2.再去检索出脚本下的微任务(不包括容器中的容器即宏任务中的微任务)并执行(微任务总是会在宏任务之前执行)。这里微任务的顺序分别是.then(function(){console.log('在Promise.then中的微任务1');})和.then(resolve=>{console.log('前面');console.log(resolve);console.log('后面')});
3.接着去检索宏任务,分别是宏任务1和宏任务2,宏任务1中有两个同步代码和一个微任务,同步代码会比微任务先执行,宏任务2中只有一个同步代码直接执行。
- 执行顺序为:
我是new Promise下会立即执行的同步代码
脚本下的同步代码
在Promise.then中的微任务1
前面
传下去微任务2的代码
后面
宏任务中的同步代码1
宏任务中的同步代码2
宏任务中的微任务3
我是第二个宏任务中的同步代码
- 演示代码四:
//<script>脚本下的第一个同步代码
console.log('开始分析吧')
//微任务1
Promise.resolve().then(function(){
console.log('在Promise.then中的微任务1');
});
//第一个宏任务--注意事件为1秒
setTimeout(function(){
console.log('宏任务中的同步代码1')
Promise.resolve().then(()=>{
console.log('宏任务中的微任务没有队可以插了')
})
console.log('宏任务中的同步代码2')
},1000);
//第二个微任务
Promise.resolve().then(function () {
console.log('我是微任务中的同步代码');
Promise.resolve().then(()=>{
console.log('微任务中的插队宏任务的微任务')
})
});
//第二个宏任务--注意时间为200ms
setTimeout(function() {
console.log("200秒的setTimeout");
setTimeout(function() {
console.log("setTimeout包含着0秒setTimeOut");
},0);
Promise.resolve().then(function() {
console.log("宏任务中微任务再次插队");
});
}, 200);
//new Promise会立即执行可以视为同步任务2
//之后跟着微任务3
new Promise((resolve)=>{
console.log('我是new Promise下会立即执行的同步代码')
resolve('传下去微任务2的代码')
}).then(resolve=>{
console.log('前面')
console.log(resolve)
console.log('后面')
});
//第三个宏任务--注意时间为0ms
setTimeout(function() {
console.log('我是第三个宏任务中的同步代码因为我耗时0ms')
}, 0);
//同步任务3
console.log('脚本下的同步代码')
- 执行的逻辑为:
1.先检索出
<script></script>脚本下的所有的同步代码并立即执行。这里的同步代码的顺序分别是console.log('开始分析吧')和new Promise((resolve)=>{console.log('我是new Promise下会立即执行的同步代码');resolve('传下去微任务2的代码');})和console.log('脚本下的同步代码')
2.再去检索出脚本下的微任务(不包括容器中的容器即宏任务中的微任务或者微任务中的微任务)并执行(微任务总是会在宏任务之前执行)。这里微任务的顺序分别是Promise.resolve().then(function(){console.log('在Promise.then中的微任务1');});和Promise.resolve().then(function(){console.log('我是微任务中的同步代码');})和.then(resolve=>{console.log('前面');console.log(resolve);console.log('后面')});
3.再去检索微任务容器中是否还有微任务,这里检索出Promise.resolve().then(()=>{console.log('微任务中的插队宏任务的微任务')})并先插队执行
4.接着去检索宏任务,这里要特别注意定时器的时间。这里一个执行的宏任务是定时为0ms的宏任务为:setTimeout(function() {console.log('我是第三个宏任务中的同步代码因为我耗时0ms')}, 0);,然后执行的第二个宏任务是定时为200ms的宏任务,该宏任务中有一个同步代码,一个微任务代码(可以插队执行),一个0ms的宏任务代码,依次执行。最后执行定时为1s的宏任务代码。
- 执行顺序为:
开始分析吧
我是new Promise下会立即执行的同步代码
脚本下的同步代码
在Promise.then中的微任务1
我是微任务中的同步代码
前面
传下去微任务2的代码
后面
微任务中的插队宏任务的微任务
我是第三个宏任务中的同步代码因为我耗时0ms
200秒的setTimeout
宏任务中微任务再次插队
setTimeout包含着0秒setTimeOut
宏任务中的同步代码1
宏任务中的同步代码2
宏任务中的微任务没有队可以插了
Promise的进阶知识之Promise.resolve和Promise.reject
- Promise.resolve()方法的参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
Promise.resolve('xxx')
// 等价于
new Promise(resolve => resolve('xxx'))
- 如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved。
const p = Promise.resolve('welcome');
p.then((r)=>{console.log(r)});
// welcome
- Promise.reject(xxx)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
Promise.reject('出错了')
.catch(e => {
console.log(e)
})
Promise的进阶知识之all和race
- .all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
- 有了all(),就可以并行的执行多个异步操作,并且在.all()后面的.then()中的回调中处理所有的返回数据。
- 但是如果一组异步数据操作中有一个出现异常则都不会进入.then()的第一个回调函数参数中。这时候尽管.then()方法拿不到数据,但是每一个传入的异步数据都会执行。与此同时还可以通过.catch()函数来捕获到.all()里最先出现的那个异常。
- 只要p1、p2之中有一个被rejected,
Promise.all([p1,p2])的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给.then()方法的回调函数。 - 演示代码一:
// 所有promise 都执行成功(resolve状态)才能通过.all()方法拿到数据;
let p1 = new Promise(resolve=>{
setTimeout(()=>{
console.log("slow");
resolve(111);
},1000)
})
let p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('fast')
resolve(222);
// reject("err");
},500)
})
console.log( Promise.all([p1,p2]))
Promise.all([p1,p2]).then(res=>{
console.log(res,'res');
}).catch((e)=>{
console.log(e,'err')
})
- 演示代码二:
let p1 = new Promise(resolve=>{
setTimeout(()=>{
console.log("slow");
resolve(111);
},1000)
})
let p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('fast')
reject("err");
resolve('状态一旦改变无法更改')
},500)
})
console.log( Promise.all([p1,p2]))
Promise.all([p1,p2]).then(res=>{
console.log(res,'res');
}).catch((e)=>{
console.log(e,'err')
})
- .race()方法会先拿到执行快的结果,不管是promise.race还是Promise.all都会执行函数体的内容(就算其中一个会抛出错误也不影响其他函数的执行并且第一个抛出错误的数据可以被catch方法捕获),区别是能否返回拿到一个有用的promise对象供后续使用。
- .race()方法只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。 演示代码一:(注意定时器的时间)
let p1 = new Promise(resolve=>{
setTimeout(()=>{
console.log("slow");
resolve(111);
},1000)
})
let p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('fast')
reject("err");
resolve('状态一旦改变无法更改')
},500)
})
console.log( Promise.race([p1,p2]),'who')
Promise.race([p1,p2]).then(res=>{
console.log(res,'res');
}).catch(err=>{
console.log(err,'err');
})
- 演示代码二:(注意定时器的时间)
let p1 = new Promise(resolve=>{
setTimeout(()=>{
console.log("slow");
resolve(111);
},100)
})
let p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('fast')
reject("err");
resolve('状态一旦改变无法更改')
},500)
})
console.log( Promise.race([p1,p2]),'who')
Promise.race([p1,p2]).then(res=>{
console.log(res,'res');
}).catch(err=>{
console.log(err,'err');
})
Promise的进阶知识之then、catch、finally
- 注意点一:catch不管被连接到哪里,都能捕获上层未捕捉过的错误。注意是未捕捉的错误,如果已经上头已经捕捉了错误,catch是不会起到作用的。Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
const promise = new Promise((resolve, reject) => {
reject("状态改变为rejected");
resolve("状态一经改变就不能再改变了");
});
//因为因为第一个then不返回值所以发生了值穿透
//第二个then中可以拿到reject("状态改变为rejected")的信息
promise.then(res => {
console.log("我能捕捉到错误吗?",res);
}).then(res=>{
console.log(res,'我能捕捉到错误吗?')
},rej=>{
console.log(rej,'我可以捕捉到错误了!')
}).catch(e=>{
console.log(e,'上头已经捕捉了我还能捕捉到吗?')
})
const promise = new Promise((resolve, reject) => {
reject("状态改变为rejected");
resolve("状态一经改变就不能再改变了");
});
//因为因为第一个then和第二个then都不返回值发生了值穿透
//所以catch可以拿到reject("状态改变为rejected")的信息
console.log( promise.then(res => {
console.log("我能捕捉到错误吗?",res);
}).then(res=>{
console.log(res,'我能捕捉到错误吗?')
}).catch(e=>{
//返回了PromiseResult为undefined的新Promise对象
console.log(e,'上头未捕捉了我还能捕捉到吗?')
}).then(r=>{
console.log(r, '最后的then')
}))
- 注意点二:.finally方法也是返回一个Promise,在它之前的链式调用的Promise无论结果状态为resolved还是rejected都会执行.finally()里面的回调函数。
- 注意点三:.finally()方法的回调函数不接收传入任何参数。它返回的始终是上一次的Promise对象值(但是如果在.finally()里抛出的是一个异常即throw new Error那么返回的是异常的Promise对象)。
- 注意点四:深层次的链式调用中
- 演示代码一:
function promise1 () {
let p = new Promise((resolve) => {
console.log('调用后的同步代码1');
resolve('传递给回调函数的参数1并改变Promise的状态')
})
return p;
}
Promise.resolve('传递给回调函数的参数4并改变Promise的状态')
//微任务1
.finally(()=>{{console.log('返回的始终是上一次的Promise对象值1111')}})
//finally()方法执行完成之后再执行这句
.then(r=>{console.log(r,'我会在哪里呢11?')})
//调用后立即执行new Promise的同步代码
//并接收到了状态为resolve的Promise对象
promise1()
//微任务2--传递给回调函数的参数1并改变Promise的状态
.then(res => console.log(res))
.catch(err => console.log(err))
//从到数第二个的微任务的finally()开始执行
.finally(() => console.log('finally1'))
Promise.resolve('传递给回调函数的参数3并改变Promise的状态')
//微任务3--传递给回调函数的参数3并改变Promise的状态我会在哪里呢22
.then(r=>{console.log(r,'我会在哪里呢22?')})
//从最后一个微任务的finally()开始执行
.finally(()=>{{console.log('返回的始终是上一次的Promise对象值1')}})
- 执行的顺序为:
调用后的同步代码1
返回的始终是上一次的Promise对象值1111
传递给回调函数的参数1并改变Promise的状态
传递给回调函数的参数3并改变Promise的状态 我会在哪里呢22?
返回的始终是上一次的Promise对象值1
finally1
传递给回调函数的参数4并改变Promise的状态 我会在哪里呢11?
- 演示代码二:
function promise1 () {
let p = new Promise((resolve) => {
console.log('调用后的同步代码1');
resolve('传递给回调函数的参数1并改变Promise的状态')
})
return p;
}
function promise2 () {
return new Promise((resolve, reject) => {
reject('传递给回调函数的参数2并改变Promise的状态')
})
}
Promise.resolve('传递给回调函数的参数4并改变Promise的状态')
//微任务1
.finally(()=>{{console.log('返回的始终是上一次的Promise对象值1111')}})
//执行完上方的.finally还要在等对象返回又会延迟
.then(r=>{console.log(r,'我会在哪里呢11?')})
//调用后立即执行new Promise的同步代码
//并接收到了状态为resolve的Promise对象
promise1()
//微任务2--传递给回调函数的参数1并改变Promise的状态
.then(res => console.log(res))
.catch(err => console.log(err))
// .then(res => console.log(res,'是不是你'))
//微任务3执行完成之后执行第一个finally(),先从上到下执行finally
.finally(() => console.log('finally1'))
promise2()
//返回的是一个状态为rejected的Promise对象所以执行的是catch语句
.then(res => console.log(res))
// 微任务3--传递给回调函数的参数2并改变Promise的状态
.catch(err => console.log(err))
//第一个finally()执行完成之后执行第二个finally(),先从上到下执行finally
.finally(() => console.log('finally2'))
- 执行的顺序为:
调用后的同步代码1
返回的始终是上一次的Promise对象值1111
传递给回调函数的参数1并改变Promise的状态
传递给回调函数的参数2并改变Promise的状态
finally1
finally2
传递给回调函数的参数4并改变Promise的状态 我会在哪里呢11?
- 演示代码三:
function promise1 () {
let p = new Promise((resolve) => {
console.log('调用后的同步代码1');
resolve('传递给回调函数的参数1并改变Promise的状态')
})
return p;
}
function promise2 () {
return new Promise((resolve, reject) => {
reject('传递给回调函数的参数2并改变Promise的状态')
})
}
Promise.resolve('传递给回调函数的参数4并改变Promise的状态')
//微任务1
.finally(()=>{{console.log('返回的始终是上一次的Promise对象值1111')}})
//在等链式调用中的finally依次执行完成之后再执行这里的then
.then(r=>{console.log(r,'我会在哪里呢11?')})
//调用后立即执行new Promise的同步代码
//并接收到了状态为resolve的Promise对象
promise1()
//微任务2--传递给回调函数的参数1并改变Promise的状态
.then(res => console.log(res))
.catch(err => console.log(err))
//微任务3执行完成之后执行第一个finally(),先从上到下执行finally
.finally(() => console.log('finally1'))
promise2()
//返回的是一个状态为rejected的Promise对象所以执行的是catch语句
.then(res => console.log(res))
// 微任务3--传递给回调函数的参数2并改变Promise的状态
.catch(err => console.log(err))
//第一个finally()执行完成之后执行第二个finally(),先从上到下执行finally
.finally(() => console.log('finally2'))
- 执行的顺序为:
调用后的同步代码1
返回的始终是上一次的Promise对象值1111
传递给回调函数的参数1并改变Promise的状态
传递给回调函数的参数2并改变Promise的状态
finally1
finally2
传递给回调函数的参数4并改变Promise的状态 我会在哪里呢11?