Event Loop中的Promise来解救”回调地狱“!Promise 是 JavaScript 中用于处理异步操作的
前言
Promise
Promise 是 JavaScript 中用于处理异步操作的一种编程模型,它代表了未来可能得到的一个结果(可能是成功的数据或失败的原因)。Promise 的主要目的是为了解决回调地狱(callback hell)问题,使异步代码更加易于理解和维护。
在详细学习Promise作用之前先简单了解一下 JavaScript 引擎处理异步操作机制
Event Loop
Event Loop
Event Loop 是 JavaScript 引擎处理异步操作的一种机制,它允许 JavaScript 运行时环境(如浏览器和 Node.js)实现非阻塞的输入输出(I/O)和其他异步操作,同时保持单线程执行模型。以下是 Event Loop 的核心概念和工作流程:
-
单线程执行:JavaScript 采用单线程执行模型,意味着同一时间只能执行一个任务。这是因为早期 JavaScript 主要用于浏览器脚本,为了避免多线程带来的复杂性(如竞态条件、死锁等),选择了单线程。
-
任务队列:Event Loop 将任务分为两大类:
- 宏任务(Macrotasks) :包括 script(整体代码)、setTimeout、setInterval、I/O、UI渲染等。
- 微任务(Microtasks) :包括
Promis、MutationObserver 等。
-
执行流程:
1、 执行全局脚本代码:首先执行当前执行环境下的脚本代码,这些代码作为第一个宏任务执行。 2、 检查微任务队列:执行完当前宏任务后,Event Loop 会查看是否有待处理的微任务。如果有,则按照先进先出的顺序执行所有微任务,直到微任务队列为空。 3、 渲染界面(可选步骤) :在某些环境中(如浏览器),在每个宏任务结束且微任务执行完毕后,有机会进行界面渲染。 4、 检查宏任务队列:接下来,Event Loop 会检查宏任务队列,取出队列中的下一个任务执行,重复上述过程。
-
循环迭代:这个过程会不断重复,形成一个循环,这就是“Event Loop”名称的由来。每次循环称为一个“tick”。
执行顺序为:start -> end -> Promise -> setTimeout。即使 setTimeout 设置了0延迟,它也是在当前宏任务结束后,且微任务队列清空之后才执行。
Event Loop 机制确保了 JavaScript 能够高效地处理并发操作,同时保持代码的可预测性和简单性。
正文
我们先来回忆一下异步执行
如下是异步编程和回调函数的使用
var data = null
function a(cb) {
setTimeout(function () {
data = 'data';
cb()
}, 1000);
}
function b(){
console.log(data);
}
a(b)
这段代码 setTimeout 创建异步操作,并通过回调函数(cb)在异步操作完成后再执行其他逻辑
其执行流程为:
- 首先,执行到
a(b),此时data仍然是null。 setTimeout开始计时,但因为是异步操作,程序不会等待这个延时结束,而是继续执行后续代码(如果有的话)。- 1秒后,
setTimeout回调执行,data的值被改为'data',然后调用b()
接着来感受一下回调地狱吧
function a(cbB,cbC,cbD){
cbB(cbC,cbD)
}
function b(cb,cbD){
cb(cbD)
}
function c(cb){
cb()
}
function d(){
}
a(b,c,d)
这段代码定义了一个函数 a,它接受三个参数,分别是三个函数 cbB、cbC 和 cbD。函数 a 的体内调用了 cbB 函数,并将 cbC 和 cbD 作为参数传递给 cbB。
又定义了一个函数 b,它接受两个参数,分别是两个函数 cb 和 cbD。函数 b 的体内调用了 cb 函数,并将 cbD 作为参数传递给 cb。
还定义了一个函数 c,它接受一个函数 cb 作为参数,并在函数体内部调用 cb 函数。
并且定义了一个无参数的函数 d。
-
函数调用:
-
a(b, c, d):调用函数a,并将函数b、c、d分别作为参数传递给它。这意味着:cbB被赋值为b,cbC被赋值为c,cbD被赋值为d。
根据
a函数的定义,它会执行cbB(cbC, cbD),即调用b(c, d)。
-
-
函数
b的执行:- 当
b被调用时,它接收到c作为cb和d作为cbD。 b函数内部调用cb(cbD),即调用c(d)。
- 当
-
函数
c的执行:c接收d作为参数,并在函数体内部调用cb(),即调用d()。
看到这四个函数调来调去的是不是感觉头晕目眩的呢?做这种项目的时候感觉来到了地狱
Promise
举个生活中的例子:起床,刷牙,吃早饭
使用情景为,在同步和异步都存在的情况下,想要函数按指定顺序输出结果
function getup() {
setTimeout(()=> {
console.log('起床');
},1000)
})
}
function wish() {
console.log('刷牙');
}
getup()
wish()
这样输出的结果为:刷牙,起床,因为JS引擎先执行同步再执行异步
为了让起床输出位置在刷牙之前,可以使用Promise模型处理异步操作
function getup() {
return new Promise((resolve,reject)=>{ // status: pending
setTimeout(()=> {
console.log('起床');
},2000)
})
}
function wish() {
console.log('刷牙');
}
getup().then(()=>{
wish();
})
.then 是 Promise 对象上的一个方法,用于指定在 Promise 完成(即变为 fulfilled 状态)时应该执行的函数,也就是当异步操作成功完成时的回调。
通过Promise和.then能够使得先执行完getup()再执行wish(),不过以上代码无法输出wish(),因为目前getup()还处于Promise的默认状态pending(等待中)
Promise 有三种状态:
- pending(等待中):初始状态,既没有成功也没有失败。
- fulfilled(已成功):表示操作成功完成。
- rejected(已失败):表示操作失败。
resolve() 函数是用来改变 Promise 的状态,从 "pending"(等待中)变为 "fulfilled"(已成功),表示异步操作已经成功完成。
function getup() {
return new Promise((resolve,reject)=>{ // status: pending
setTimeout(()=> {
console.log('起床');
resolve()
},2000)
})
}
function wish() {
console.log('刷牙');
}
getup().then(()=>{
wish();
})
接下来再和上述过程一样的添加eat()函数即可
嵌套多层.then 可以实现输出异步操作的值,但在大型项目中采用该方法
function getup() {
return new Promise((resolve,reject)=>{ // status: pending
setTimeout(()=> {
console.log('起床');
resolve()
},2000)
})
}
function wish() {
return new Promise((resolve,reject)=>{ // status: pending
setTimeout(()=> {
console.log('刷牙');
resolve()
},2000)
})
}
function eat(){
console.log('吃早饭');
}
getup().then(()=>{
wish().then(()=>{
eat()
})
})
.then 方法也可以链式调用,使得处理异步操作变得更加流畅和易于阅读。
但需要注意,要让链式调用不出错误,必须返回调用的函数(即return)
getup()
.then(() =>{
return wish()
})
.then(() =>{
eat()
})
除此之外.then它有两个参数:第一个参数是处理成功情况(resolve)的回调函数,第二个参数(可选)是处理失败情况(reject)的回调函数。
当你调用 resolve() 函数时,你可以传递一个参数,这个参数通常代表异步操作的结果,它会被传递给 .then 方法中注册的回调函数。
function a(){
return new Promise((resolve,reject)=>{
setTimeout(function() {
console.log('a is ok');
data = 'a'
resolve('gagaggag')
reject('no')
},2000)
})
}
function b(){
console.log(data);
}
// a().then((b))
a().then((res) => {
console.log(res);// gagaggag
b() // a
})
.catch((err) => {
console.log(err); // 只有在发生错误的时候才会执行 no
})
如果异步操作(如数据获取)失败,reject('no') 会被调用,错误信息 'no' 会被传递给 .catch 方法处理,从而可以在控制台看到相应的错误消息。
其中
reject状态只有在发生错误的时候才会执行.catch可以有效避免程序直接报错,而导致系统崩溃
总结
Promise
Promise 是 JavaScript 中用于处理异步操作的一种编程模型,它代表了未来可能得到的一个结果(可能是成功的数据或失败的原因)。Promise 的主要目的是为了解决回调地狱(callback hell)问题,使异步代码更加易于理解和维护。
.then
是Promise原型上的一个函数,x.then() then函数会在x这个promise实例对象状态变更为
resolved之后才能执行内部逻辑,由此借助这个机制可以将异步捋为同步
then方法支持链式调用,因为then默认也会返回一个promise实例对象,但是状态默认为pending,这就导致后面的then用不上前面then的状态,从而继续向前查找- 我们在
then中返回一个promise实例对象,那么这个promise实例对象就会作为then的返回值,将覆盖then自带的返回值 reslove(x)这个x会指定交给then中的回调函数reject(x)这个x会指定交给catch中的回调函数,catch是专门用来捕获程序中的错误的,而不会让程序崩溃
转载自:https://juejin.cn/post/7385430107290927115