此名字暂不可见
No distraction,it's you and me

这是标题

2024-11-20

第一次写博客,文笔有点烂,也不知道要说些什么,各位见谅一下吧😋

1. js执行机制

1.1 小谈一下

​ js是一门单线程语言,执行程序就像在食堂吃饭排队,必须一个一个来。这样看上去充满秩序,但在面对像定时器这样需要等待处理的任务时就会卡住,这就好比你在做饭时需要煮饭洗菜炒菜,而你必须等煮完饭才能去洗菜炒菜,这期间你什么都干不了。这样相当浪费时间,因此js将任务分为了同步和异步两种类型。

1.2 同步

​ 所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。

举个小例子:你是一名CCNU的学生。有一天,你想改善伙食,但无奈捉襟见肘。于是你给你家人打了个电话,只要接通,你就有money拿,但对方没有接通。你不停地打电话,因为没接通就没有生活费,没有生活费就没有豪赤的。最后一天过去了,电话没打通,你只能吃6块钱的营养套餐😡。

​ 由此可见,同步的好处在于能够马上得到返回的结果,但必须一个接一个的执行。一旦中间哪步少了或者出错,程序就会卡住,白白浪费时间。

1.3 异步

​ 异步,与同步相对,当一个异步过程调用发出后,在没有得到结果之前,可以继续执行后续操作。

有一个小例子:你是一名CCNU的学生。有一天,你想改善伙食,但无奈捉襟见肘。于是你给你家人发了一条短信,告诉他们你没米了,让他们速速转钱。虽然他们当时在忙没看到,但到了饭点,你还是收到了转账。最后,你happy地吃上了8块钱地营养套餐😋。

​ 不难看出,异步虽然返回结果慢一些,但它能够在等待的同时执行后续操作,避免了时间的浪费。


​ 在执行过程中,js会先执行同步代码,待其全部完毕后,再执行异步代码。

​ 看上去好像很对,实际上不对。正如人与人不能一概而论,异步任务之间亦有差别。有些异步任务需要连贯执行,不能分开。

​ 想象一下,你在超市买了很多东西,结果在结账时只接了一件,就被别人插队了,还要你重新去排,这不红温😡?为了解决这个问题,异步任务又被分为了宏任务和微任务。


1.4 宏任务

​ 很好理解,宏任务指的就是不需要连贯执行的任务,例如setTimeout。

​ 它就像你的寒假作业,你可以做一天,躺一天,只要能做完就行(虽然我一般不做)。

1.5 微任务

​ 相对的,微任务指的就是需要连贯执行的任务,例如Promise。

​ 它就像你的期末考试,不能考一天,躺一天,你必须连着把它考完,而且期考都在寒假作业前结束。


因此,在js实际执行过程中,js会先执行同步代码,遇到异步宏任务则将其放入宏任务队列中,遇到异步微任务则将其放入微任务队列中,此时二者不分优先级。待所有同步代码执行完,再将异步微任务从队列中调入主线程执行。待微任务执行完后,再将异步宏任务从队列中调入主线程执行。


2. Promise

​ 刚才好像提到一下Promise,不知道该说点什么,就讲一下Promise吧😊

2.1 回调地狱

​ 在这之前,先讲一下回调函数。当一个函数被当做一个实参传入到另一个函数,并且这个函数在外部函数内被调用,这个被用来完成某些任务的函数,就是回调函数。当一个回调函数嵌套一个回调函数就形成了一个嵌套结构,而嵌套多了就形成了回调地狱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function(我是1){
function(我是2){
function(我是3){
function(我是4){
function(我是5){
function(我是6){
function(我是7){
function(我是8){
...
}
}
}
}
}
}
}
}

非常好回调,使我的大脑旋转。

为了不让程序员在面对这串代码时一脸懵逼,ES6提出了一种新的解决方案,那就是promise。

2.2 Promise的参数和状态

​ Promise诞生的目的就是更优雅地书写异步任务 (优雅永不过时)。它是一个构造函数,因此在调用时可以实例化。它接收函数类型的参数,这个参数又接受两个参数,一个是resolve,一个是reject,分别接受成功和失败时的回调。在Promise的内部又有三种状态(state),分别是pending(原始态),fulfilled(成功态)和rejected(失败态)。其中状态只能由pending转为fulfilled或rejected,fulfilled和rejected既不能变回pending,也不能相互转换。

​ 举个例子:new Promise是薛定谔的猫,猫活着就是resolve,寄了就是reject。其中pending是既活又寄的叠加态,打开容器后,猫既可以活着变为fulfilled,也可以寄了变为rejected,但是猫的状态已经确定,你不能用两级反转再把猫变回“寄-活叠加态”,也不能用死者苏生从墓地复活猫或是把哈基米变成哈寄米😡。

2.3 Promise的实例方法

​ Promise实例对象有成功的结果(value)和失败的结果(reason),如果外部代码想要访问这个成功或失败的值,就需要用到Promise的实例方法了。

2.3.1 then

​ then是Promise的一个实例方法,它能接受最多两个参数—onFulfilled和onRejected,通过它们我们可以拿到成功或失败时的结果,同时返回一个新的Promise实例对象。因为又会返回一个实例对象,因此then支持链式调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let promise = new Promise((resolve,reject) => {
resolve(`6`)
reject(`7`)
})

promise.then(
(value) => {
return value + `6`
},
(reason) => {
return reason + `7`
}
).then(
(value) => {
console.log(value);
},
(reason) => {
console.log(reason);
}
)
// => 66

​ 可以看到then的执行依赖于上一步的结果,如果其中任意一步出错就会使整个程序出错。


​ 一般来说,我们在执行程序时都会希望他是成功的,因此一步一个reject不仅写起来麻烦,而且看起来也比较麻烦。为了省略reject,我们就用到了Promise的另一种方法。

2.3.2 catch

​ catch用于在Promise失败或抛出错误时执行某些操作,它只接受一个失败的回调函数作为参数,并返回一个Promise对象。与then相同,catch同样支持链式调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	 let promise = new Promise((resolve,reject) => {
let num = Math.floor(Math.random() * 11)
if(num > 5){
resolve(`对了`)
}else{
reject(`错了`)
}
})
promise.then(
(value) =>{
console.log(value);
}).catch((error) =>{
throw new Error(error)
} )
//当num > 5时
//-> 对了

//否则
//-> Uncaught (in promise) Error: 错了


​ 到这里,我们已经可以用then处理正确的结果,用catch处理错误或异常的结果。但实际上,有些操作无论结果正确或是失败都得执行。为此,我们又用到了finally方法。

2.3.3 finally

​ finally会在Promise状态决定时返回一个新的Promise对象,而不在意当前Promise的状态如何。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let promise = new Promise((resolve,reject) => {
let num = Math.floor(Math.random() * 11)
if(num > 5){
resolve(`我对了吗?`)
}else{
reject(`我错了吗?`)
}
})
promise.then(
(value) =>{
console.log(value);
}
).catch((error) =>{
throw new Error("我错了吗");

}).finally(()=>{
console.log(`反正我完了`);

})


总结一下这三位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
let promise = new Promise((resolve,reject) => {
let num = Math.floor(Math.random() * 11)
if(num > 5){
resolve(`我是林克`)
}else{
reject(`没醒`)
}
})

promise.then((value)=>{
console.log(value);
value = `我醒了`
return value
}).then((value) =>{
console.log(value);
value =`我找到了同伴`
return value
}).then((value)=>{
console.log(value);
value =`我打倒了反派`
return value
}).then((value)=>{
console.log(value);
value = `我找到了圣兽`
return value
}).then((value)=>{
console.log(value);
value = `我打倒了盖依,救出了塞尔达`
return value
}).catch((error)=>{
console.log(error);

}).finally(()=>{
console.log(`反正塞尔达又会被抓了`);
})

2.4 Promise静态方法

2.4.1 Promise.all

​ Promise.all接受一个 Promise 可迭代对象作为输入(一般是数组),并返回一个 新的promise对象。当所有输入的 Promise 都被成功时,返回的 Promise 也将被成功(空的也行),并返回一个包含所有成功结果的数组;如果输入的任意一个 Promise 失败,则返回的 Promise 也将失败,并带有第一个失败的原因。

1
2
3
4
5
6
7
8
9
10
11
const promise1 = Promise.resolve(6)
const promise2 = Promise.resolve(6)
const promise3 = Promise.resolve(6)
Promise.all([promise1,promise2,promise3]).then((value) => console.log(value))
//->[6,6,6]

const promise1 = Promise.resolve(6)
const promise2 = Promise.resolve(6)
const promise3 = Promise.reject(`错了`)
Promise.all([promise1,promise2,promise3]).then((value) => console.log(value))
//-> Uncaught (in promise) 错了

2.4.2 Promise.allSettled

​ Promise.allSettled接受一个 Promise 可迭代对象作为输入(一般是数组),无论输入的Promise对象结果如何,都会返回一个 新的promise对象,并带有一个包含所有结果的对象数组(包括状态)。

1
2
3
4
5
6
7
8
		const promise1 = Promise.resolve(`芜`)
const promise2 = Promise.reject(`湖`)
Promise.allSettled([promise1,promise2]).then((value) => console.log(value))
//->
[
{status: 'fulfilled', value: '芜'}
{status: 'rejected', reason: '湖'}
]

2.4.3 Promise.any

​ Promise.all接受一个 Promise 可迭代对象作为输入(一般是数组),并返回一个 新的promise对象。当输入的 Promise 任意一个成功时,返回的 Promise 也将被成功(空的也行),并返回第一个成功的返回值;如果输入的所有 Promise 失败,则返回的 Promise 也将失败,并带有一个失败的原因。

1
2
3
4
5
6
7
8
9
10
        const promise1 = Promise.reject(0);
const promise2 = Promise.resolve(`芜湖`)
const promise3 = Promise.resolve(`湖芜`)
Promise.any([promise1, promise2, promise3]).then((value) => console.log(value));
//-> 芜湖
const promise1 = Promise.reject(`错了吗`);
const promise2 = Promise.reject(`错了吧`)
const promise3 = Promise.reject(`应该全错了`)
Promise.any([promise1, promise2, promise3]).then((value) => console.log(value));
//-> Uncaught (in promise) AggregateError: All promises were rejected

2.4.4Promise.race

​ Promise.race接受一个 Promise 可迭代对象作为输入,并返回一个 Promise。这个返回的 promise的状态 会随着第一个 promise 的状态的决定而决定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
	function writeHomework(time,action){
return new Promise((resolve,reject) => {
setTimeout(() => {
return resolve(action)
} ,time)
})
}

const promise1 = writeHomework(`100`,`睡觉`)
const promise2 = writeHomework(`10000`,`写作业`)
Promise.race([promise1,promise2]).then((value) => console.log(value))
//-> 睡觉

function writeHomework(time,action){
return new Promise((resolve,reject) => {
setTimeout(() => {
return reject(action)
} ,time)
})
}

const promise1 = writeHomework(`100`,`睡觉`)
const promise2 = writeHomework(`10000`,`写作业`)
Promise.race([promise1,promise2]).then((value) => console.log(value))
//-> Uncaught (in promise) 睡觉

2.4.5 Promise.reject

Promise.reject会返回一个失败的 Promise 对象,同时将值作为失败的原因。

1
2
3
4
5
6
7
const p = Promise.reject(`错了`)
//等于
const p = new Promise((resolve,reject) => reject(`错了`))

const a = Promise.reject(`错了`)
.catch(error => console.log(error === `错了`))
//true

2.4.6 Promise.resolve

Promise.resolve会将给定的值转换为一个Promise对象。如果该值本身就是一个Promise,就将该Promise返回;如果该值是一个thenable对象(有用then方法的对象),Promise.resolve将调用其then方法及其两个回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	const promise1 = Promise.resolve(`芜湖`)
promise1.then((value) => console.log(value))
//-> 芜湖

const promise1 = new Promise((resolve,reject) => resolve(7777))
const promise2 = Promise.resolve(promise1)
promise2.then((value) => console.log(value))
//=> 7777

const thenable = {
then: function(resolve, reject) {
resolve(42);
}
};

const promise1 = Promise.resolve(thenable);
promise1.then((value) => console.log(value))
//-> 42

小结

​ Promise.all 相当于宝可梦中成功集齐八个道馆徽章才能挑战联盟,少一个即为失败。

​ Promise.allSettled 相当于一颗苹果树,不管好坏,只要结苹果就会把结果返回。

​ Promise.any 相当于你期考时写对一点沾边的内容都算你有分,但你一点不对就全错。

​ Promise.race 相当于一场竞赛,第一个到终点的决定结果。

​ Promise.reject 感觉像教材上学过的亚里士多德的名言,返回的全是错的.

​ Promise.resolve 。。。我不知道啊,返回“主 === 6”算了


3.async和await

​ 书接上回,Promise名义上解决的回调地狱的问题,但实际在代码中依然充斥着大量的then函数。因此,async和await便诞生了,它们存在的意义便是让代码更加简洁。

3.1async

​ async是一个通过异步执行并隐式返回Promise作为结果的函数。async的使用很简单,只需要在函数前加上async关键字来表示函数是异步的。而异步函数的调用和普通函数相同,加括号调用就行。

1
2
3
4
5
6
7
8
9
	async function yibu(){
return '我是异步'
}
console.log(yibu())

// [[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "我是异步"

​ 由上可知,async返回的Promise对象带有state和result,原因是调用异步函数时,内部会调用Promise.resolve()方法将它转化为一个Promise对象返回;如果异步函数内部抛出错误,内部就会调用Promise.reject()返回一个Promise对象。

1
2
3
4
5
6
7
	async function yibu2(){
throw new Error(`我错了`)
}
console.log(yibu2())
//[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: Error: 我错了

如果想要获取async函数的结果,就可以调用then或catch。

1
2
3
4
5
6
7
8
	async function yibu3(){
return `又是我`
}
yibu3.then(value => console.log(value))
console.log(`我先去了`)

//我先去了
//又是我

可以看到,then能够获取async的结果并放入异步队列,且同步代码先于异步代码执行。


3.2await

​ 根据MDN的定义,await 用于等待一个 Promise 成功并获取它成功之后的值。用人话说,await只能放入async函数中,并在之后接上一个能够返回Promise对象的表达式。它等待Promise对象执行完毕,并返回结果。事实上,await可以用于任意表达式,所以await后面可以接普通函数,只是不常用而已。

1
2
3
4
5
6
7
8
9
10
	function what(){
return `这是什么`
}

async function question(){
const promise1 = await what()
console.log(promise1)
}
question()
//这是什么

​ 事实上,await在等待一个Promise对象时,会阻塞函数后面的代码,等待Promise对象resolve/reject,然后得到resolve/reject的值,作为await表达式的结果;如果等到了一个非Promise对象,那么它会隐式地返回一个resolve的Promise对象。

3.3它们有毛线用呢

​ 还是那句老话,async和await能够使代码更简洁。

​ 举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	function wuhu(name){
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve(name + `,` + `李在赣神魔`)
}),2000})
}

async function test(){
let result1 = await wuhu(`蔚`)
let result2 = await wuhu(`泽丽`)
let result3 = await wuhu(`瑞兹`)
console.log([result1,result2,result3]);
}
test()
//[ "蔚,李在赣神魔"
"泽丽,李在赣神魔"
"瑞兹,李在赣神魔"]

​ 你可能没看出来,那再举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function wuhu(name){
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve(name + `,` + `李在赣神魔`)
}),2000})
}

wuhu(`蔚`).then((value) =>{
console.log(value);
})
wuhu(`泽丽`).then((value) =>{
console.log(value);
})
wuhu(`瑞兹`).then((value) =>{
console.log(value);
})

​ 显然,第二种写法更加抽象,当then使用足够多时,你绝对不会想看下去,所以await在处理then链式调用方面还是很有用的。


3.2(补)阻塞

​ 再来仔细说一下阻塞。当遇到await时,会阻塞函数内部(不是全部)处于它后面的代码,再去执行函数外部的同步代码;当外部的同步代码全部执行完成,再回到改函数执行剩余的代码。当然,微任务队列的代码会优先处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	function who(value){
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve(value)
},4000)
})
}

async function after(){
let promise1 = await who('369:')
console.log(promise1);
console.log(`我被阻塞了`);

}
after()
console.log(`我没被阻塞`)
//我没被阻塞
// 4s后 ->'369:' 我被阻塞了

​ 讲一下执行过程:

调用after函数,里面遇到了await,代码就暂停到这,不再继续执行,等待后面的who(’369:’)执行完毕,4秒后返回了结果值赋值给promise1并打印,暂停结束,代码继续执行console.log语句。

​ 而且,js引擎在等待Promise.resolve时,并没有真正的停止工作,它还可以处理其他一些事情。比如在after()函数后console.log()一下,我们会发现外部的console.log()语句会先执行。

​ 再来一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
	async function first(){
console.log(`我先来的`);
await second()
console.log(`我最后`);
setTimeout(() => {
console.log(`第一定时器`);
},0)
}

async function second(){
setTimeout(() => {
console.log(`第二定时器`);

},0)
console.log(`再到我`);
}

first()

setTimeout(() => {
console.log(`第三定时器`);
},0)

console.log(`我在第三`);
// 我先来的
// 再到我
// 我在第三
// 我最后
// 第二定时器
// 第三定时器
// 第一定时器

​ 再讲一下执行过程:

首先执行first(),打印出”我先来的”。

然后遇到await second(),在second()中遇到第二定时器,将其加入宏任务队列,在执行同步代码打印”再到我”。

因为seconde()阻塞了后面代码的执行,所以后面代码加入微任务队列。再到第三定时器,将其加入宏任务队列,之后打印“我在第三”。

因为同步代码执行完成,先执行微任务队列,打印“我最后”,再将第一定时器加入宏任务队列。

最后执行宏任务队列,按照先进先出的原则依次打印。

补充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	const first = () => (new Promise((resolve, reject) => {
console.log(3);
let p = new Promise((resolve, reject) => {
console.log(7);
setTimeout(() => {
console.log(5);
resolve(6);
console.log(8)
}, 0)
resolve(1);
});
resolve(2);
p.then((arg) => {
console.log(arg);
});
}));

first().then((arg) => {
console.log(arg);
});

console.log(4);
//3,7,4,1,2,5,8

Author: John Doe

Link: https://159357254680.github.io/2024/11/20/%E8%BF%99%E6%98%AF%E6%A0%87%E9%A2%98/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
硬绑定方法
NextPost >
Hello World
CATALOG
  1. 1. 1. js执行机制
    1. 1.1. 1.1 小谈一下
    2. 1.2. 1.2 同步
    3. 1.3. 1.3 异步
    4. 1.4. 1.4 宏任务
    5. 1.5. 1.5 微任务
  2. 2. 2. Promise
    1. 2.1. 2.1 回调地狱
    2. 2.2. 2.2 Promise的参数和状态
    3. 2.3. 2.3 Promise的实例方法
      1. 2.3.1. 2.3.1 then
      2. 2.3.2. 2.3.2 catch
      3. 2.3.3. 2.3.3 finally
    4. 2.4. 2.4 Promise静态方法
      1. 2.4.1. 2.4.1 Promise.all
      2. 2.4.2. 2.4.2 Promise.allSettled
      3. 2.4.3. 2.4.3 Promise.any
      4. 2.4.4. 2.4.4Promise.race
      5. 2.4.5. 2.4.5 Promise.reject
      6. 2.4.6. 2.4.6 Promise.resolve
      7. 2.4.7. 小结
    5. 2.5. 3.async和await
    6. 2.6. 3.1async
    7. 2.7. 3.2await
    8. 2.8. 3.3它们有毛线用呢
    9. 2.9. 3.2(补)阻塞
    10. 2.10. 补充