Event Loop中的Promise来解救”回调地狱“!

零 JavaScript教程评论78字数 4405阅读14分41秒阅读模式

Event Loop中的Promise来解救”回调地狱“!

前言

Promise

Promise 是 JavaScript 中用于处理异步操作的一种编程模型,它代表了未来可能得到的一个结果(可能是成功的数据或失败的原因)。Promise 的主要目的是为了解决回调地狱(callback hell)问题,使异步代码更加易于理解和维护。

在详细学习Promise作用之前先简单了解一下 JavaScript 引擎处理异步操作机制Event Loop文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html

Event Loop

Event Loop 是 JavaScript 引擎处理异步操作的一种机制,它允许 JavaScript 运行时环境(如浏览器和 Node.js)实现非阻塞的输入输出(I/O)和其他异步操作,同时保持单线程执行模型。以下是 Event Loop 的核心概念和工作流程:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html

  1. 单线程执行:JavaScript 采用单线程执行模型,意味着同一时间只能执行一个任务。这是因为早期 JavaScript 主要用于浏览器脚本,为了避免多线程带来的复杂性(如竞态条件、死锁等),选择了单线程。
  2. 任务队列:Event Loop 将任务分为两大类:
    • 宏任务(Macrotasks) :包括 script(整体代码)、setTimeout、setInterval、I/O、UI渲染等。
    • 微任务(Microtasks) :包括 Promis、MutationObserver 等。
  3. 执行流程

    1、 执行全局脚本代码:首先执行当前执行环境下的脚本代码,这些代码作为第一个宏任务执行。 2、 检查微任务队列:执行完当前宏任务后,Event Loop 会查看是否有待处理的微任务。如果有,则按照先进先出的顺序执行所有微任务,直到微任务队列为空。 3、 渲染界面(可选步骤) :在某些环境中(如浏览器),在每个宏任务结束且微任务执行完毕后,有机会进行界面渲染。 4、 检查宏任务队列:接下来,Event Loop 会检查宏任务队列,取出队列中的下一个任务执行,重复上述过程。

  4. 循环迭代:这个过程会不断重复,形成一个循环,这就是“Event Loop”名称的由来。每次循环称为一个“tick”。

执行顺序为:start -> end -> Promise -> setTimeout。即使 setTimeout 设置了0延迟,它也是在当前宏任务结束后,且微任务队列清空之后才执行。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html

Event Loop 机制确保了 JavaScript 能够高效地处理并发操作,同时保持代码的可预测性和简单性。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html

正文

我们先来回忆一下异步执行

如下是异步编程和回调函数的使用文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html

js

复制代码
var data = null
function a(cb) {
    setTimeout(function () {
        data = 'data';
        cb()
    }, 1000);
}
function b(){
    console.log(data);
}
a(b)

这段代码 setTimeout 创建异步操作,并通过回调函数(cb)在异步操作完成后再执行其他逻辑文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html

其执行流程为:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html

  • 首先,执行到 a(b),此时 data 仍然是 null
  • setTimeout 开始计时,但因为是异步操作,程序不会等待这个延时结束,而是继续执行后续代码(如果有的话)。
  • 1秒后,setTimeout 回调执行,data 的值被改为 'data',然后调用 b()

接着来感受一下回调地狱吧

js

复制代码
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,它接受三个参数,分别是三个函数 cbBcbC 和 cbD。函数 a 的体内调用了 cbB 函数,并将 cbC 和 cbD 作为参数传递给 cbB文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html

又定义了一个函数 b,它接受两个参数,分别是两个函数 cb 和 cbD。函数 b 的体内调用了 cb 函数,并将 cbD 作为参数传递给 cb文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html

还定义了一个函数 c,它接受一个函数 cb 作为参数,并在函数体内部调用 cb 函数。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16334.html

并且定义了一个无参数的函数 d

  1. 函数调用
    • a(b, c, d):调用函数 a,并将函数 bcd 分别作为参数传递给它。这意味着:
      • cbB 被赋值为 b
      • cbC 被赋值为 c
      • cbD 被赋值为 d

      根据 a 函数的定义,它会执行 cbB(cbC, cbD),即调用 b(c, d)

  2. 函数 b 的执行
    • 当 b 被调用时,它接收到 c 作为 cb 和 d 作为 cbD
    • b 函数内部调用 cb(cbD),即调用 c(d)
  3. 函数 c 的执行
    • c 接收 d 作为参数,并在函数体内部调用 cb(),即调用 d()

看到这四个函数调来调去的是不是感觉头晕目眩的呢?做这种项目的时候感觉来到了地狱

Promise

举个生活中的例子:起床,刷牙,吃早饭

使用情景为,在同步和异步都存在的情况下,想要函数按指定顺序输出结果

js

复制代码
function getup() {
        setTimeout(()=> {
            console.log('起床');
        },1000)
    })
}

function wish() {
        console.log('刷牙');
}
getup()
wish()

这样输出的结果为:刷牙,起床,因为JS引擎先执行同步再执行异步

为了让起床输出位置在刷牙之前,可以使用Promise模型处理异步操作

js

复制代码
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"(已成功),表示异步操作已经成功完成。

js

复制代码
function getup() {
    return new Promise((resolve,reject)=>{ // status: pending
        setTimeout(()=> {
            console.log('起床');
            resolve()
        },2000)
    })
}
function wish() {
        console.log('刷牙');
}

getup().then(()=>{
     wish();
 })

接下来再和上述过程一样的添加eat()函数即可

嵌套多层.then 可以实现输出异步操作的值,但在大型项目中采用该方法

js

复制代码
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)

js

复制代码
getup()
.then(() =>{
    return wish()
})
.then(() =>{
    eat()
})

除此之外.then它有两个参数:第一个参数是处理成功情况(resolve)的回调函数,第二个参数(可选)是处理失败情况(reject)的回调函数。

当你调用 resolve() 函数时,你可以传递一个参数,这个参数通常代表异步操作的结果,它会被传递给 .then 方法中注册的回调函数。

js

复制代码
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://www.0s52.com/bcjc/javascriptjc/16334.html
    本社区资源仅供用于学习和交流,请勿用于商业用途
    未经允许不得进行转载/复制/分享

发表评论