这里写目录标题
- 1. 预备知识
- 2. Promise
- 3. Promise的几个关键问题
- 4. 事件循环(Event Loop)
- 参考
1. 预备知识
1.1 实例对象与函数对象
- 实例对象:new 函数产生的对象,称为实例对象,简称为对象
- 函数对象:将函数作为对象使用时,称为函数对象
function Fn() { // Fn只能称为函数
}
const fn = new Fn() // Fn只有new过的才可以称为构造函数
//fn称为实例对象
console.log(Fn.prototype)// Fn作为对象使用时,才可以称为函数对象
Fn.bind({}) //Fn作为函数对象使用
$('#test') // $作为函数使用
$.get('/test') // $作为函数对象使用
()左边是函数,点左边是对象(函数对象、实例对象)
1.2 异步编程
JS语言是单线程机制,所谓单线程就算按次序执行,执行完一个任务再执行下一个。JS中存在同步
和异步
两种操作,这两种操作其实都是在一条流水线上(单线程),只是这两种操作在单线程上的执行顺序不一样罢了!当js触发到异步任务时,会将异步任务交给浏览器处理,当执行有结果时,会把异步任务的回调函数插入待处理队列的队尾!
异步
:异步就是从主线程发射一个子线程来完成任务,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的.
在实际的任务中,需要有一个子线程来完成一些可能消耗时间长的任务,但由于子线程独立于主线程,所以即使出现阻塞也不会影响主线程的运行。但是子线程有一个局限:一旦发射了以后就会与主线程失去同步,我们无法确定它的结束,如果结束之后需要处理一些事情,比如处理来自服务器的信息,我们是无法将它合并到主线程中去的。
为了解决这个问题,JS的异步操作函数是通过回调函数来实现异步任务的结果处理。(菜鸟教程)
1.3 两种类型的回调函数
回调函数定义:
一个函数被当做一个实参传入到另一个函数(外部函数),并且这个函数在外部函数内被调用,用来完成某些任务的函数。就称为回调函数
- 同步回调
立即执行,完全执行完了才结束,不会放入回调队列中
const arr = [1,2,3]
arr.forEach(item=>{ // 遍历回调,同步回调,不会放入队列,一上来就要执行
console.log(item)
})
console.log('forEach()之后')
- 异步回调
不会立即执行,会放入回调队列中将来执行
定时器回调 / ajax回调 / Promise成功或失败的回调
// 定时器回调
setTimeout(() => { // 异步回调,会放入队列中将来执行
console.log('timeout callback()')
}, 0)
console.log('setTimeout()之后')
new Promise((resolve, reject)=>{
resolve(1)
}).then(
value => { console.log('value',value)},
reason => {console.log('reason',reson)}
)
console.log('----------')
js 引擎先把初始化的同步代码都执行完成后,才执行回调队列中的代码
1.4 回调地狱
回调地狱:当一个回调函数嵌套一个回调函数的时候就会出现一个嵌套结构当嵌套的多了就会出现回调地狱的情况。
案例:
比如我们发送三个 ajax 请求:
第一个正常发送
第二个请求需要第一个请求的结果中的某一个值作为参数
第三个请求需要第二个请求的结果中的某一个值作为参数
$.ajax({
url: '我是第一个请求',
type: 'get',
success (res) {
// 现在发送第二个请求
$.ajax({
url: '我是第二个请求',
type:'post',
data: { a: res.a, b: res.b },
success (res1) {
// 进行第三个请求
$.ajax({
url: '我是第三个请求',
type:'post',
data: { a: res1.a, b: res1.b },
success (res2) {
console.log(res2)
}
})
}
})
}
})
setTimeout(()=>{
console.log('第一层'); //等3秒打印“第一层”,再执行下一个回调函数
setTimeout(()=>{
console.log('第二层');
setTimeout(()=>{
console.log('第三层');
},1000)
},2000)
},3000)
回调地狱是为了让我们代码执行顺序的一种操作(解决异步),但是它会使我们的可读性非常差。为了解决这个问题,就需要引入Promise
去解决回调地狱问题。
2. Promise
异步回调的问题:
- 之前处理异步是通过纯粹的回调函数的形式进行处理
- 很容易进入到回调地狱中,剥夺了函数return的能力
- 问题可以解决,但是难以读懂,维护困难
- 稍有不慎就会踏入回调地狱 - 嵌套层次深,不好维护
Promise的优势:
- promise是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外)
- 并未剥夺函数return的能力,因此无需层层传递callback,进行回调获取数据
- 代码风格,容易理解,便于维护
- 多个异步等待合并便于解决
2.1 理解Promise
抽象表达:Promise是JS中进行异步编程的新的解决方案(旧方案是单纯使用回调函数)
---- 异步编程 ①fs 文件操作 ②数据库操作 ③Ajax ④定时器
具体表达:
①从语法上看:Promise是一个构造函数 (自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法)
②从功能上看:promise对象用来封装一个异步操作并可以获取其成功/失败的结果值
阮一峰解释:
Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise对象有以下两个特点:
-
对象的状态不受外界影响。
Promise对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。 -
一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。
只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为resolved
(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
总结:
- 对象的状态不受外界影响,共有三种状态,分别是:
pending
、fulfilled
、rejected
,pending
可以转换成另外两种状态。 - 只有这两种,且一个 promise 对象只能改变一次
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果
- 无论成功还是失败,都会有一个结果数据。成功的结果数据一般称为
value
,而失败的一般称为reason
。
2.2 Promise的基本流程
2.3 Promise的基本使用
2.3.0 Promise构造函数:Promise(executor) {}
- executor 函数:同步执行
(resolve, reject) => {}
- resolve 函数:内部定义成功时调用的函数
resove(value)
- reject 函数:内部定义失败时调用的函数
reject(reason)
说明:executor
是执行器,会在 Promise 内部立即同步回调,异步操作 resolve/reject
就在 executor
中执行
创造一个Promise实例
const promise = new Promise(((resolve, reject) => {
//resolve和reject是排斥的关系
if (/* 异步操作成功 */){
resolve("first"); // resolve 表示成功的回调
}else {
reject("error") // reject 表示失败的回调
}
})).then(
// 接收得到成功的value数据 onResolved
value=>{
console.log(value);
},
// 接收得到失败的reason数据 onRejected
reason=>{
console.log(reason);
})
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
2.3.1 resolve和reject
resolve
函数的作用是,将Promise
对象的状态从“未完成”变为“成功”(即从 pending
变为 resolved
),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject
函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending
变为 rejected
),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数(两个回调函数):
第一个回调函数是Promise对象的状态变为resolved
时调用
第二个回调函数是Promise对象的状态变为rejected
时调用。
这两个函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value); //done
});
上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。
2.3.2 执行顺序
Promise新建后就会立即执行
let promise = new Promise((resolve, reject) => {
console.log('promise'); //构造函数
resolve();
});
promise.then(()=>{
console.log('resolve');
});
console.log('hi!');
// Promise
// Hi!
// resolved
上面代码中,Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。
注意,调用resolve
或reject
并不会终结 Promise 的参数函数的执行。
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
一般来说,调用resolve
或reject
以后,Promise 的使命就完成了,后继操作应该放到then
方法里面,而不应该直接写在resolve
或reject
的后面。所以,最好在它们前面加上return
语句,这样就不会有意外。
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})
上面代码中,resolve
函数是带有参数的,其除了可以是正常值外,还可是另一个Promise实例,比如下面这样:
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
上面代码中,p1和p2都是 Promise 的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。
注意,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。
2.3.3 两个应用
异步加载图片
function loadImageAsync(url){
return new Promise((resolve, reject)=>{
const image = new Image()
image.onload = function() {resolve(image)}
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
}
image.src = url
})
}
实现Ajax操作
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function(){
if (this.readyState !== 4){
return
}
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
const client = new XMLHttpRequest()
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
})
return promise
}
getJSON('/posts.json').then(
res => {
console.log('Contents: ' + json);
},err => {
console.error('出错了', error);
})
上面代码中,getJSON是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。
2.4 Promise.prototype.then()
Promise.prototype.then()
作用:为 Promise 实例添加状态改变时的回调函数。
参数:p.then(onResolved, onRejected)
可选的
onResolved函数:resolved
状态的回调函数,成功的回调函数 (value) => {}
onRejected:rejected
状态的回调函数,失败的回调函数 (reason) => {}
then
方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)
因此可以采用链式写法,即then
方法后面再调用另一个then
方法。
使用 Promise 的链式调用解决回调地狱
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
上面的代码使用then
方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
采用链式的then
,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
Promise链式方法
const promise = new Promise(((resolve, reject) => {
console.log("first layor");
resolve("second layor");
//reject("error")
})).then(
res=>{
console.log('res:',res);
//开下一层,三种方法一样
// return new Promise((resolve, reject) => {
// resolve('third layor')
// })
// return Promise.resolve('third layor')
return {mess:'third layor'}
},
err=>{
console.log('err:',err);
}).then(
res=>{
console.log('res:',res);
console.log('res.mess:',res.mess);
},
err=> {
console.log("rejected: ", err)
})
上面代码中,创建了一个Promise对象,调用resolve函数,promise
状态变为 resolved
, 第一个then
方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then
方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved
,就调用第一个回调函数(res部分),如果状态变为rejected
,就调用第二个回调函数(err部分)。
第四种方法
function timeout(mess, t){
return new Promise((resolve, reject) => {
setTimeout(()=>{
resolve(mess)
}, t*1000);
})
}
const promise = new Promise(((resolve, reject) => {
console.log("first layor");
//如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。
resolve("second layor");
//reject("error")
})).then(
res => {
//res是从resolve()函数传过来的
console.log('res:',res);
//开下一层,三种方法一样
// return new Promise((resolve, reject) => {
// resolve('third layor')
// })
// return Promise.resolve('third layor')
// return {mess:'third layor'}
return timeout('third layor',1)
}, err => {
//err是从reject()函数传过来的
console.log(err);
}).then(res => {
console.log('res:',res);
//console.log('res.mess:',res.mess);
});
2.5 Promise.prototype.catch()
Promise.prototype.catch()
作用:用于指定发生错误时的回调函数。
参数:p.catch(onRejected)
onRejected
函数:失败的回调函数 (reason) => {}
Promise.prototype.catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,
写法:
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同于
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
用法:
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
上面代码中,getJSON()方法返回一个 Promise 对象,如果该对象状态变为resolved
,则会调用then()
方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected
,就会调用catch()
方法指定的回调函数,处理这个错误。另外,then()
方法指定的回调函数,如果运行中抛出错误,也会被catch()
方法捕获。
const promise = new Promise((resolve, reject) => {
throw new Error('test error')
}).catch(error=>{
console.log(error);
})
//Error: test error
//上面的写法等同于下面两种写法
// 写法一
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// 写法二
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
如果 Promise 状态已经变成resolved
,再抛出错误是无效的。
const promise = new Promise((resolve, reject) => {
resolve('ok')
throw new Error('test error')
}).then(value=>{
console.log(value);
}).catch(error=>{
console.log(error);
})
//ok
上面代码中,Promise 在resolve
语句后面,再抛出错误,不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。
冒泡性质:
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});
上面代码中,一共有三个 Promise 对象:一个由getJSON()产生,两个由then()产生。它们之中任何一个抛出的错误,都会被最后一个catch()捕获。
最佳写法:
一般来说,不要在then()
方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch
方法。
即
// bad
promise.then(
function(data) {
// success
}, function(err) {
// error
});
//等价于
promise.then(
value => {……},
reason => {……}
)
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
//等价于
promise
.then(value => { //cb
// success
})
.catch(reason => {
// error
});
//第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch()方法,而不使用then()方法的第二个参数。
跟传统的try/catch代码块不同的是,如果没有使用catch()
方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
浏览器遇到错误,不会退出进程、终止脚本执行,2秒后还是会输出123。
因此,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。
下面这个例子是未捕获的错误
const promise = new Promise(function (resolve, reject) {
resolve('ok');
setTimeout(function () { throw new Error('test') }, 0)
});
promise.then(function (value) { console.log(value) });
// ok
// Uncaught Error: test
上面代码中,Promise 指定在下一轮“事件循环”再抛出错误。到了那个时候,Promise 的运行已经结束了,所以这个错误是在 Promise 函数体外抛出的,会冒泡到最外层,成了未捕获的错误。
一般总是建议,Promise 对象后面要跟catch()
方法,这样可以处理 Promise 内部发生的错误。catch()
方法返回的还是一个 Promise 对象,因此后面还可以接着调用then()
方法。
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing()
.catch(function(error){
console.log('oh no', error);
})
.then(function() {
console.log('everything is great');
}).;
// oh no [ReferenceError: x is not defined]
// everything is great
上面代码运行完catch()
方法指定的回调函数,会接着运行后面那个then()
方法指定的回调函数。如果没有报错,则会跳过catch()
方法,执行then()
方法。
Promise.resolve()
.catch(function(error) {
console.log('oh no', error);
})
.then(function() {
console.log('carry on');
});
// carry on
上面代码里的Promise对象没有报错,跳过catch()
方法,直接执行后面的then()
方法。
但是要是then()
方法出现错误,前一个catch()
是无法捕获的。
catch()
内部可以抛出一个错误,若之后没有别的catch()
,则这个错误无法捕获,因此catch()
之后还可以再接catch()
,来捕获前一个catch()
方法抛出的错误
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
//改写前
someAsyncThing().then(function() {
return someOtherAsyncThing();
}).catch(function(error) {
console.log('oh no', error);
// 下面一行会报错,因为 y 没有声明
y + 2;
}).then(function() {
console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// 无catch()捕获前一个catch()的错误
//改写后
someAsyncThing().then(function() {
return someOtherAsyncThing();
}).catch(function(error) {
console.log('oh no', error);
// 下面一行会报错,因为y没有声明
y + 2;
}).catch(function(error) {
console.log('carry on', error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]
2.6 Promise.all(iterable)
iterable
:包含 n 个 Promise 的可迭代对象,如 Array
或 String
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
Promise.all()
方法接受一个数组作为参数,每个数组元素都是一个Promise实例。也接收参数不是数据,但必须具有Iterator接口,且返回的每个成员都是Promise实例
写法:
const p = Promise.all([p1,p2,p3])
let p1 = new Promise((resolve,reject)=>{
resolve('OK');
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
const p = Promise.all([p1,p2,p3])
console.log(p)
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.reject('Error');
let p3 = Promise.resolve('Oh Yeah');
const result = Promise.all([p1, p2, p3]);
console.log(result);
说明:p的状态由p1,p2,p3决定
(1)只有p1、p2、p3的状态都变成fulfilled
,p的状态才会变成fulfilled
,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected
,p的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p的回调函数。
注意,如果作为参数的 Promise 实例,自己定义了catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e)
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e)
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了] 两个Promise实例的返回值组成数组
上面代码中,p1会resolved
,p2首先会rejected
,但是p2有自己的catch
方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch
方法后,也会变成resolved
,导致Promise.all()
方法参数里面的两个实例都会resolved
,因此会调用then
方法指定的回调函数,而不会调用catch
方法指定的回调函数。
如果p2没有自己的catch
方法,就会调用Promise.all()
的catch
方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 报错了
2.7 Promise.race(iterable)
iterable
:包含 n 个 Promise 的可迭代对象,如 Array
或 String
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
说明:返回一个新的 Promise,第一个完成的 Promise的结果状态就是最终的结果状态
谁先完成就输出谁(不管是成功还是失败)
写法
const p = Promise.race([p1, p2, p3]);
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
const p2 = Promise.resolve(2)
const p3 = Promise.reject(3)
const p = Promise.race([p1, p2, p3])
p.then(value => {
console.log('race onResolved()', value)
})
.catch(reason => {
console.log('race onRejected()', reason)
});
console.log(p)
//race onResolved() 2
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
2.8 Promise.allSettled()
Promise.allSettled()
:一组异步操作都结束了,不管每一个操作是成功还是失败,都可以进行下一步操作。
Promise.all()
方法是所有异步操作都成功的情况,就可以进行下一步,但如果有一个操作失败,就无法进行下一步。
const urls = [url_1, url_2, url_3];
const requests = urls.map(x => fetch(x));
try {
await Promise.all(requests);
console.log('所有请求都成功。');
} catch {
console.log('至少一个请求失败,其他请求可能还没结束。');
}
上面示例中,Promise.all()
可以确定所有请求都成功了,但是只要有一个请求失败,它就会报错,而不管另外的请求是否结束。
但如果我们希望等到一组异步操作结束了,不管其操作成功还是失败,再进行下一步操作,我们可以使用Promise.allSettled()
方法
Promise.allSettled()
方法接受一个数组作为参数,每个数组元素都是一个Promise实例,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled
还是rejected
),返回的 Promise 对象才会发生状态变更。
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
上面代码中,Promise.allSettled()
的返回值allSettledPromise
,状态只可能变成fulfilled。它的回调函数接收到的参数是数组results。该数组的每个成员都是一个对象,对应传入Promise.allSettled()
的数组里面的两个 Promise 对象。
成员对象的status
属性的值只可能是字符串fulfilled
或字符串rejected
,用来区分异步操作是成功还是失败。如果是成功(fulfilled
),对象会有value
属性,如果是失败(rejected
),会有reason
属性,对应两种状态时前面异步操作的返回值。
2.9 Promise.any()
Promise.any()
方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。
Promise.any([
fetch('https://v8.dev/').then(() => 'home'),
fetch('https://v8.dev/blog').then(() => 'blog'),
fetch('https://v8.dev/docs').then(() => 'docs')
]).then((first) => { // 只要有一个 fetch() 请求成功
console.log(first);
}).catch((error) => { // 所有三个 fetch() 全部请求失败
console.log(error);
});
Promise.any()
跟Promise.race()
方法很像,只有一点不同,就是Promise.any()
不会因为某个 Promise 变成rejected
状态而结束,必须等到所有参数 Promise 变成rejected
状态才会结束。
2.10 Promise.resolve(value)
作用:将现有对象转为 Promise 对象
value
:将被 Promise 对象解析的参数,也可以是一个成功或失败的 Promise 对象
返回:返回一个带着给定值解析过的 Promise 对象,如果参数本身就是一个 Promise 对象,则直接返回这个 Promise 对象。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
- 参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve
将不做任何修改、原封不动地返回这个实例。
let p2 = Promise.resolve(new Promise((resolve, reject) => {
// resolve('OK'); // 成功的Promise
reject('Error');
}));
p2.catch(reason => {
console.log(reason);
})
//Error
- 参数是非Promise类型的对象, 则返回的结果为成功Promise对象
let p = Promise.resolve(521);
console.log(p); // Promise {<fulfilled>: 521}
p.then(value=>{
console.log(value) //521
})
- 参数是一个
thenable
对象
thenable
对象指的是具有then
方法的对象,比如下面这个对象。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
Promise.resolve()
方法会将这个对象转为 Promise 对象,然后就立即执行thenable
对象的then()
方法。
et thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function (value) {
console.log(value); // 42
});
- 不带任何参数,直接返回一个
resolved
状态的 Promise 对象。
const p = Promise.resolve();
p.then(function () { //p是一个Promise 对象。
// ...
});
注意:立即resolve()
的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
上面代码中,setTimeout(fn, 0)
在下一轮“事件循环”开始时执行,Promise.resolve()
在本轮“事件循环”结束时执行,console.log('one')
则是立即执行,因此最先输出。
2.11 Promise.reject(reason)
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected
。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, (err) => console.log("rejected:", err));
// rejected: 出错了
Promise.reject()
方法的参数,会原封不动地作为reject
的理由,变成后续方法的参数。
Promise.reject('出错了')
.catch(e => {
console.log(e === '出错了')
})
// true
上面代码中,Promise.reject()
方法的参数是一个字符串,后面catch()
方法的参数e就是这个字符串。
3. Promise的几个关键问题
3.1 如何改变 promise 的状态?
(1) resolve(value):如果当前是 pending
就会变为 resolved
(2) reject(reason):如果当前是 pending
就会变为 rejected
(3) 抛出异常:如果当前是 pending
就会变为 rejected
const p = new Promise((resolve, reject) => {
//resolve(1) // promise变为resolved成功状态
//reject(2) // promise变为rejected失败状态
throw new Error('出错了') // 抛出异常,promise变为rejected失败状态,reason为抛出的error
})
p.then(
value => {},
reason => {console.log('reason',reason)}
)
// reason Error:出错了
3.2 一个 promise 指定多个成功/失败回调函数,都会调用吗?
当 promise 改变为对应状态时都会调用
const p = new Promise((resolve, reject) => {
//resolve(1)
reject(2)
})
p.then(
value => {},
reason => {console.log('reason',reason)}
)
p.then(
value => {},
reason => {console.log('reason2',reason)}
)
// reason 2
// reason2 2
3.3 改变 promise 状态和指定回调函数谁先谁后?
都有可能,常规是先指定回调再改变状态,但也可以先改状态再指定回调
- 如何先改状态再指定回调?
(1)在执行器中直接调用resolve()/reject()
(2)延迟更长时间才调用then()
let p = new Promise((resolve, reject) => {
// setTimeout(() => {
resolve('OK');
// }, 1000); // 有异步就先指定回调,否则先改变状态
});
p.then(value => {
console.log(value);
},reason=>{
})
- 什么时候才能得到数据?
(1)如果先指定的回调,那当状态发生改变时,回调函数就会调用得到数据
(2)如果先改变的状态,那当指定回调时,回调函数就会调用得到数据
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1) // 改变状态
}, 1000)
}).then( // 指定回调函数 (先指定)
value => {},
reason =>{}
)
此时,先指定回调函数,保存当前指定的回调函数;后改变状态(同时指定数据),然后异步执行之前保存的回调函数。
new Promise((resolve, reject) => {
resolve(1) // 改变状态
}).then( // 指定回调函数
value => {},
reason =>{}
)
这种写法,先改变的状态(同时指定数据),后指定回调函数(不需要再保存),直接异步执行回调函数
3.4 promise.then()
返回的新 promise 的结果状态由什么决定?
(1)简单表达:由 then()
指定的回调函数执行的结果决定
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
console.log(value);
}, reason => {
console.warn(reason);
});
console.log(result);
(2)详细表达:
① 如果抛出异常,新 promise 变为 rejected,reason 为抛出的异常
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
//1. 抛出错误
throw '出了问题';
}, reason => {
console.warn(reason);
});
console.log(result);
② 如果返回的是非 promise 的任意值,新 promise 变为 resolved,value 为返回的值
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
//2. 返回结果是非 Promise 类型的对象
return 521;
}, reason => {
console.warn(reason);
});
console.log(result);
③ 如果返回的是另一个新 promise,此 promise 的结果就会成为新 promise 的结果
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
//3. 返回结果是 Promise 对象
return new Promise((resolve, reject) => {
// resolve('success');
reject('error');
});
}, reason => {
console.warn(reason);
});
console.log(result);
3.5 Promise 如何串联多个操作任务?
(1)Promise 的 then()
返回一个新的 Promise ,可以并成 then()
的链式调用
(2)通过 then
的链式调用串联多个同步/异步任务
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
return new Promise((resolve, reject) => {
resolve("success");
});
}).then(value => {
console.log(value); // success
}).then(value => {
console.log(value); // undefined
})
// success
// undefined
new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行任务1(异步)')
resolve(1)
}, 1000)
}).then(
value => {
console.log('任务1的结果', value)
console.log('执行任务2(同步)')
return 2 // 同步任务直接return返回结果
}
).then(
value => {
console.log('任务2的结果', value)
return new Promise((resolve, reject) => { // 异步任务需要包裹在Promise对象中
setTimeout(() => {
console.log('执行任务3(异步)')
resolve(3)
}, 1000)
})
}
).then(
value => {
console.log('任务3的结果', value)
}
)
// 执行任务1(异步)
// 任务1的结果 1
// 执行任务2(同步)
// 任务2的结果 2
// 执行任务3(异步)
// 任务3的结果 3
3.6 Promise 异常穿透(传透)?
(1)当使用 Promise 的 then
链式调用时,可以在最后指定失败的回调
(2)前面任何操作出了异常,都会传到最后失败的回调中处理
new Promise((resolve, reject) => {
//resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
return 2
}
).then(
value => {
console.log('onResolved2()', value)
return 3
}
).then(
value => {
console.log('onResolved3()', value)
}
).catch(
reason => {
console.log('onRejected1()', reason)
}
)
// onRejected1() 1
相当于这种写法:多写了很多reason => {throw reason}
new Promise((resolve, reject) => {
//resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
return 2
},
reason => {throw reason} // 抛出失败的结果reason
).then(
value => {
console.log('onResolved2()', value)
return 3
},
reason => {throw reason} // 抛出失败的结果reason
).then(
value => {
console.log('onResolved3()', value)
},
reason => {throw reason} // 抛出失败的结果reason
).catch(
reason => {
console.log('onRejected1()', reason)
}
)
// onRejected1() 1
所以失败的结果是一层一层处理下来的,最后传递到 catch
中。
或者,将 reason => {throw reason}
替换为 reason => Promise.reject(reason)
也是一样的
3.7 中断 Promise 链?
当使用 Promise 的 then
链式调用时,在中间中断,不再调用后面的回调函数
办法:在回调函数中返回一个 pending
状态的 Promise 对象
new Promise((resolve, reject) => {
//resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
return 2
}
).then(
value => {
console.log('onResolved2()', value)
return 3
}
).then(
value => {
console.log('onResolved3()', value)
}
).catch(
reason => {
console.log('onRejected1()', reason)
}
).then(
value => {
console.log('onResolved4()', value)
},
reason => {
console.log('onRejected2()', reason)
}
)
// onRejected1() 1
// onResolved4() undefined
为了在 catch
中就中断执行,可以这样写:
new Promise((resolve, reject) => {
//resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
return 2
}
).then(
value => {
console.log('onResolved2()', value)
return 3
}
).then(
value => {
console.log('onResolved3()', value)
}
).catch(
reason => {
console.log('onRejected1()', reason)
return new Promise(() => {}) // 返回一个pending的promise
}
).then(
value => {
console.log('onResolved4()', value)
},
reason => {
console.log('onRejected2()', reason)
}
)
// onRejected1() 1
在 catch
中返回一个新的 Promise,且这个 Promise没有结果。
由于,返回的新的 Promise 结果决定了后面 then 中的结果,所以后面的 then
中也没有结果。
这就实现了中断 Promise 链的效果。
4. 事件循环(Event Loop)
4.1单线程
javascript是一门单线程的非阻塞的脚本语言。这是由其最初的用途来决定的:与浏览器交互。
单线程: 即javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务。
非阻塞: 当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。
javascript引擎如何实现”非阻塞“?
解决方法 —— event loop(事件循环)
4.2 事件循环机制(Event Loop)
事件循环机制:JS代码的执行顺序,是指浏览器或Node的一种解决JS单线程运行时不会阻塞的一种机制。
事件循环分为两种,分别是浏览器事件循环和node.js事件循环。
JS的主要运行环境:浏览器和Node.js。
浏览器的事件循环又分为同步任务和异步任务
4.2.1 同步任务与异步任务
-
同步任务
含义:在主线程上排队执行的任务,只有一个任务执行完毕,才能执行后一个任务。 -
异步任务
含义:不进入主线程,而进入“任务队列(task queue)”的任务,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
分类:异步任务又分为宏任务和微任务。所有同步任务都在主线程上执行,形成一个函数调用栈(执行栈),而异步则先放到任务队列(task queue)里,任务队列又分为宏任务(macro-task)与微任务(micro-task)。
1). 宏任务
宏任务包括:script(整体代码)、setTimout、setInterval、setImmediate(node.js环境)、I/O、UI交互事件
2). 微任务
微任务包括:new Promise()的then/catch/finally(回调)、async/await中的await, MutationObserver(html5新特新)、Object.observe(已废弃)、queueMicrotask(微任务队列)、process.nextTick(node环境)。若同时存在promise和nextTick,则先执行nextTick。
4.2.2 执行过程
组成:执行栈、任务队列(分为宏队列和微队列)、浏览器的Web APIs
JS 中用来存储待执行回调函数的队列包含 2 个不同特定的列队:宏队列和微队列
- 宏列队: 用来保存待执行的宏任务(回调), 比如: 定时器回调/DOM 事件回调/ajax 回调
- 微 列 队 : 用 来 保 存 待 执 行 的 微 任 务 ( 回 调 ), 比 如 : promise 的 回 调/MutationObserver 的回调
图解
事件循环过程:
3. 所有同步任务都在主线程上执行,形成一个执行栈(调用栈);
4. JS引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。浏览器中的各种 Web API 为异步的代码提供了一个单独的运行空间, Web API接收异步事件的回调函数。
5. 当一个异步事件返回结果后,其返回结果不会立即添加到执行栈中,而是会被放到一个任务队列中,然后,根据这个异步事件的类型,其事件回调放到对应的宏任务队列或者微任务队列中去(队列遵循先进先出得原则)。
6. 在当前执行栈为空的时候,主线程会查看微任务队列是否有事件存在。
7. 如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;
8. 如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈…如此反复,进入循环。
注意:当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
4.2.3 执行流程
- 先执行同步代码,
- 遇到异步宏任务则将异步宏任务放入宏任务队列中,
- 遇到异步微任务则将异步微任务放入微任务队列中,
- 当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,
- 微任务执行完毕后再将异步宏任务从队列中调入主线程执行,
- 一直循环直至所有任务执行完毕。
4.3 async 与 await
4.3.1 async 函数
作用:声明一个函数是异步的
- 函数的返回值为 promise 对象
- promise 对象的结果由 async 函数执行的返回值决定
- 如果返回的是一个非 Promise 类型的数据, 则async 函数返回 promise 的状态 为 fulfilled 成功。
- 如果返回的是一个 Promise对象,则 async 函数返回 promise 的状态由返回的Promise对象的状态决定。
- 如果 throw Errow 抛出异常,则 async 函数返回 promise 的状态为 rejected 失败。
async function foo() {
return 1;
}
//等价于
function foo() {
return Promise.resolve(1);
}
async function fn1(){
//return 1
//throw 2
//return Promise.reject(3)
return Promise.resolve(3)
}
const result = fn1()
console.log(result )
result.then(
value => {
console.log('resolve:',value)
},
reason => {
console.log('reject:',reason )
})
async 函数的函数体可以被看作是由 0 个或者多个 await 表达式分割开来的。从第一行代码直到(并包括)第一个 await 表达式(如果有的话)都是同步运行的。
这样的话,一个不含 await 表达式的 async 函数是会同步运行的。
然而,如果函数体内有一个 await 表达式,async 函数就一定会异步执行。
async function foo() {
await 1;
}
//等价于
function foo() {
return Promise.resolve(1).then(() => undefined);
}
在 await 表达式之后的代码可以被认为是存在在链式调用的 then 回调中,多个 await 表达式都将加入链式调用的 then 回调中,返回值将作为最后一个 then 回调的返回值。
在接下来的例子中,我们将使用 await 执行两次 promise,整个 foo 函数的执行将会被分为三个阶段。
- foo 函数的第一行将会同步执行,await 将会等待 promise 的结束。然后暂停通过 foo 的进程,并将控制权交还给调用 foo 的函数。
- 一段时间后,当第一个 promise 完结的时候,控制权将重新回到 foo 函数内。示例中将会将1(promise 状态为 fulfilled)作为结果返回给 await 表达式的左边即 result1。接下来函数会继续进行,到达第二个 await 区域,此时 foo 函数的进程将再次被暂停。
- 一段时间后,同样当第二个 promise 完结的时候,result2 将被赋值为 2,之后函数将会正常同步执行,将默认返回undefined 。
async function foo() {
const result1 = await new Promise((resolve) =>
setTimeout(() => resolve("1"))
);
const result2 = await new Promise((resolve) =>
setTimeout(() => resolve("2"))
);
}
foo();
4.3.2 await 表达式
作用:等待一个异步函数执行完成
语法:await expression;
expression
: 要等待的 Promise 实例,Thenable 对象,或任意类型的值。
返回值:
- 如果表达式是 promise 对象, await 返回的是 promise 成功的值
- 如果await后是一个普通的值,那么会直接返回这个值。
- 如果await后是一个thenable对象,会构造一个新 Promise 用于等待,构造时会调用该对象的then方法,再来决定后续的值。
- 如果await后面的表达式返回的是reject状态,那么会将这个reject结果作为函数的promise的reject值。
function fn1(){
//return 1
//throw 2
//return Promise.reject(3)
//return Promise.resolve(3)
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(4)
},1000)
})
}
async function fn3(){
const value = await fn1()
console.log('value:',value);
}
fn3()
//value: 4
注意:
- await 必须写在 async 函数中, 但 async 函数中可以没有 await
- 如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try…catch 捕获处理
4.3.3 await对执行过程的影响
不管await后面跟着的是什么,await都会阻塞后面的代码
当函数执行到 await 时,被等待的表达式会立即执行,所有依赖该表达式的值的代码会被暂停,并推送进微任务队列(microtask queue)。然后主线程被释放出来,用于事件循环中的下一个任务。即使等待的值是已经敲定的 promise 或不是 promise,也会发生这种情况。例如,考虑以下代码:
async function foo(name) {
console.log(name, "start"); //同步执行
await console.log(name, "middle"); //立即执行
console.log(name, "end"); //异步执行,加入微任务队列
}
foo("First");
foo("Second");
// First start
// First middle
// Second start
// Second middle
// First end
// Second end
上面的代码执行到 await 时,后面的代码就会整体被安排进一个新的微任务,此后的函数体变为异步执行。
在含有await的async中,同一代码块中await后的所有代码将被放置返回的promise的then
方法中执行,也即await后的代码将被加载进微任务队列,同时不含await的async函数与普通函数没有任何差别。
上面代码对应的 Promise 写法是:
function foo(name) {
return new Promise((resolve) => {
console.log(name, "start");
resolve(console.log(name, "middle"));
}).then(() => {
console.log(name, "end");
});
}
4.4 事件循环案例(面试)
案例1:
setTimeout(() => {
console.log("4");
setTimeout(() => {
console.log("8");
}, 0);
new Promise((r) => {
console.log("5");//构造函数是同步的
r();
}).then(() => {
console.log("7");//then()是异步的,这里已经入队
});
console.log("6");
}, 0);
new Promise((r) => {
console.log("1");//构造函数是同步的
r();
}).then(() => {
console.log("3");//then()是异步的,这里已经入队
});
console.log("2");
//输出顺序:1 2 3 4 5 6 7 8
案例2:
setTimeout(function () { //宏任务
console.log(1);
});
new Promise(function(resolve,reject){ //微任务
console.log(2)
resolve(3)
}).then(function(val){
console.log(val);
})
//2
//3
//1
解释:
- 遇到setTimout,异步宏任务,放入宏任务队列中;
- 遇到new Promise,Promise在实例化的过程中所执行的代码都是同步进行的,所以输出2;
- 而Promise.then中注册的回调才是异步执行的,将其放入微任务队列中
- 遇到同步任务console.log(‘4’);输出4;主线程中同步任务执行完
- 从微任务队列中取出任务到主线程中,输出3,微任务队列为空
- 从宏任务队列中取出任务到主线程中,输出1,宏任务队列为空,结束~
案例3:
setTimeout(()=>{
new Promise(resolve =>{
resolve();
}).then(()=>{
console.log('test');
});
console.log(4);
});
new Promise(resolve => {
resolve();
console.log(1)
}).then( () => {
console.log(3);
Promise.resolve().then(() => {
console.log('before timeout');
}).then(() => {
Promise.resolve().then(() => {
console.log('also before timeout')
})
})
})
console.log(2);
//输出:1 2 3 before timeout also before timeout 4 test
解释:
- 遇到setTimeout,异步宏任务,将() => {console.log(4)}放入宏任务队列中;
- 遇到new Promise,Promise在实例化的过程中所执行的代码都是同步进行的,所以输出1;
- 而Promise.then中注册的回调才是异步执行的,将其放入微任务队列中
- 遇到同步任务console.log(2),输出2;主线程中同步任务执行完
- 从微任务队列中取出任务到主线程中,输出3,此微任务中又有微任务,Promise.resolve().then(微任务a).then(微任务b),将其依次放入微任务队列中;
- 从微任务队列中取出任务a到主线程中,输出 before timeout;
- 从微任务队列中取出任务b到主线程中,任务b又注册了一个微任务c,放入微任务队列中;
- 从微任务队列中取出任务c到主线程中,输出 also before timeout;微任务队列为空
- 从宏任务队列中取出任务到主线程,此任务中注册了一个微任务d,将其放入微任务队列中,接下来遇到输出4,宏任务队列为空
- 从微任务队列中取出任务d到主线程 ,输出test,微任务队列为空,结束
案例3:
console.log("script start");
setTimeout(() => {
console.log("setTimeout1");
new Promise((resolve) => {
resolve()
}).then(() => {
new Promise((resolve) => {
resolve()
}).then(() => {
console.log("then1");
})
console.log("then2");
})
});
new Promise((resolve) => {
console.log("promise1");
resolve()
}).then(() => {
console.log("then3");
})
setTimeout(function () {
console.log("setTimeout2");
});
queueMicrotask(() => {
console.log("queueMicrotask1")
});
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("script end");
//输出script start promise1 script end then3 queueMicrotask1 then4 setTimeout1 then2 then1 setTimeout2
解释:
- script start; 同步代码直接执行
- setTimeout定时器加入宏任务队列
- promise1; Promise执行体里边的代码是同步的,直接执行,把then加入微任务队列
- setTimeout继续加入宏任务队列
- queueMicrotask微任务方法加入微任务队列
- Promise的then加入微任务队列
- script end; 同步代码直接执行
- 同步代码执行完毕,开始执行微任务队列
- then3; queueMicrotask1; then4; 微任务队列执行完毕,开始执行第一个宏任务
- setTimeout1; 宏任务中同步代码直接执行
- setTimeout中Promise.then加入微任务队列
- setTimeout中微任务队列执行, 把then中的Promise.then加入微任务队列
- then2; then中的同步代码直接执行,然后开始执行微任务队列
- then1; setTimeout中任务队列执行完毕
- setTimeout2; 执行第二个宏任务
- 所有任务队列执行完毕
案例4(含async和await):
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('settimeout');
}, );
async1();
new Promise((resolve, reject) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
})
解释:
- 遇到console.log(‘script start’); 输出script start
- setTimeout继续加入宏任务队列
- 执行async1()函数; 同步代码直接执行,输出async1 start
- 遇到await async2(), 执行async2()函数; 同步代码直接执行,输出async2
- await后的代码加入微任务队列
- new Promise执行体里边的代码是同步的,直接执行,输出 promise1
- Promise的then加入微任务队列
- 同步代码执行完毕,开始执行微任务队列
- 微任务队列第一个任务 console.log(‘async1 end’); 输出 async1 end
- 输出 promise2
- 微任务队列结束,执行第一个宏任务setTimeout; 输出 settimeout
案例5(含async和await):
async function func1() {
console.log('1')
await func2();
setTimeout(()=> console.log('7'), 500);
console.log('4');
}
async function func2() {
console.log('2');
setTimeout(()=> console.log('6'),500)
}
func1()
new Promise((resolve, reject)=>{
console.log(('3'));
resolve()
}).then(()=>{
console.log('5');
})
//输出:1 2 3 4 5 6 7