切换语言为:繁体

如何优雅的在 JavaScript 中 使用 Event Loop与Promise 控制异步流程

  • 爱糖宝
  • 2024-07-04
  • 2075
  • 0
  • 0

在现代JavaScript开发中,异步编程已成为不可或缺的一部分,它使我们能够处理耗时操作,如网络请求、文件读写,而不阻塞主线程。在最近的学习中了解到了Promise和Event Loop,本文将深入探讨他们的工作原理、如何优雅地使用Promise控制异步流程,以及其在JavaScript事件循环机制中的角色。

异步编程的需求

在Web开发或其他涉及I/O操作的场景中,经常遇到需要等待某个操作完成才能继续执行的情况。传统的解决方案是使用回调函数,但随着业务复杂度上升,回调函数层层嵌套,形成了所谓的“回调地狱”,这不仅使得代码难以阅读和维护,还可能引发调试难题。

如下面代码,要求需要执行完a再执行b再执行c执行d(假设每个方法中都存在setTimeout),当嵌套无限多时,简直就是地狱。

function a(cbB,cbC,abD){
    cbB(cbC,cbD)
}

function b(cb,cbD){
    cb(cbD)
}

function c(cb){
    cb()
}

function d(){

}

a(b,c,d)

Promise的诞生

Promise,正如其名,代表了一个未来的值,它或者成功(resolved)并携带结果数据,或者失败(rejected)并携带错误信息。Promise的设计初衷就是为了解决异步编程中的复杂性问题,提供一种更优雅、链式调用的方式来组织异步操作。

Promise的基本使用

通过new Promise((resolve, reject) => {...})构造函数创建,其中resolvereject是两个函数,分别用于改变Promise的状态。

我以一个案例来给大家讲解一下。当getup()状态为resolve().then()中的computer()开始执行。

function getup() {
    //pending resloved rejected
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('小帅起床了');
            resolve()
        }, 2000)
    })
}

function computer() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('小帅打开了电脑');
            resolve()
        }, 1000)
    })
}

function study(params) {
    console.log('小帅开始敲代码了');
}

getup().then(() => {
    return computer()
})
.then(() => {
    study()
})

执行结果

如何优雅的在 JavaScript 中 使用 Event Loop与Promise 控制异步流程

.then方法

是promise原型上的一个函数,x.then函数会在x这个promise实例对象状态变更为resolved之后才执行内部逻辑,由此借助这个机制可以将异步捋成同步。then方法支持链式调用,因为then默认也会返回一个promise对象,但状态默认是pending,这就会导致后面的then用不上前面then的状态,从而继续往前查找。我们在then中返回一个promise对象,会覆盖掉then自带的返回。

还是以上面代码为例,删除computer前的return。为什么执行结果是先敲代码再打开电脑呢?

因为第一个then没有返回值,但是它会默认返回一个Promise,但这个Promise的状态默认是pending(进行中),第二个then无法使用到第一个then的状态,那么第二个then就会继续往前查找,发现getup()状态为resolve,此时它就会立即执行,而第一个then则在2s后执行。

不删除return时,第一个then返回的是computer()Promise,此时第二个then可以识别到第一个then中的状态为resolve,所以会等待computer()执行完后再执行

getup().then(() => {
    computer()
})
.then(() => {
    study()
})

执行结果

如何优雅的在 JavaScript 中 使用 Event Loop与Promise 控制异步流程

.catch方法

用于捕获Promise链中任何环节抛出的错误,保证异常处理的一致性。

如一下代码,如果Promise的状态为reject时,会导致程序报错,增加.catach()方法捕捉错误,就会将错误处理不会再导致程序报错。

function a(){
    return new Promise(function(resloved,reject){
        setTimeout(function(){
            console.log('a is ok');
            // resloved('请求到的数据')
            reject('错误')
        },1000)
    })
}
function b() {
    console.log(1);
}

a().then((res)=>{
    console.log(res);
    b()
})
.catch((err)=>{
    console.log(err,'xxxx');
})

执行结果

有catch时

如何优雅的在 JavaScript 中 使用 Event Loop与Promise 控制异步流程

无catch

如何优雅的在 JavaScript 中 使用 Event Loop与Promise 控制异步流程

浏览器引擎

JavaScript引擎和渲染引擎都是现代浏览器的重要组成部分。

  • JavaScript引擎负责解析和执行JavaScript代码。它让网页具有交互性,处理用户事件,执行异步操作,以及操纵网页文档对象模型(DOM)等。知名的JavaScript引擎例如Google Chrome的V8、Mozilla Firefox的SpiderMonkey、Apple Safari的JavaScriptCore(也被称作Nitro或SquirrelFish)等。

  • 渲染引擎(又称为布局引擎或呈现引擎)则负责解析HTML和CSS,构建网页的可视化表示,并对其进行布局和绘制。它确保网页内容按照CSS样式和网页标准进行正确显示。常见的渲染引擎有Blink(用于Chrome和Opera)、Gecko(用于Firefox)、WebKit(用于Safari)以及历史上Internet Explorer的Trident(也称MSHTML)和Microsoft Edge早期版本的EdgeHTML。

事件循环(Event Loop)

事件循环是JavaScript处理异步操作的核心机制,确保了即使在单线程环境下也能高效地管理任务执行顺序。

任务类型

  • 宏任务(Macro Task) :包括setTimeoutsetIntervalsetImmediate(Node.js环境)、I/O操作、UI渲染等,它们在当前执行栈完成后执行。

  • 微任务(Micro Task) :如Promise的回调、process.nextTick(Node.js)、MutationObserver等,它们在当前执行栈的末尾执行,优先级高于宏任务。

执行流程

  1. 初始化阶段:程序开始执行时,调用栈是空的,但底部隐含了全局执行上下文。同时,微任务队列和宏任务队列也是初始状态,宏任务队列中通常有一个代表整个脚本的宏任务。

  2. 执行脚本:全局执行上下文被推入调用栈,开始同步执行脚本中的代码。执行过程中,任何产生的宏任务或微任务会被分别放入对应的队列中。

  3. 脚本执行完毕:当当前宏任务(通常是整个脚本)执行结束,调用栈清空至全局上下文,此时检查微任务队列。

  4. 执行微任务:如果微任务队列不为空,则从队首开始执行微任务,直至队列为空,期间新产生的微任务也会立即执行。此过程会持续进行,直到没有更多的微任务加入队列。

  5. 宏任务执行:微任务全部执行完毕后,从宏任务队列中取出下一个任务(如果有),将其推入调用栈执行。执行过程中,可能继续产生新的宏任务和微任务。

  6. 渲染操作:在每次宏任务执行后,如果事件循环检测到渲染时机(即调用栈为空且没有待执行的微任务),渲染引擎会尝试更新界面。

  7. 循环继续:事件循环不断重复上述过程,检查队列,执行任务,直到所有任务(包括宏任务和微任务)都被处理完。

通过这样的机制,JavaScript能够高效地管理同步和异步代码,确保在单线程环境中既能够执行复杂的异步操作,又不阻塞UI渲染或其他任务的执行。

结语

Event Loop机制支撑起了JavaScript的异步执行模型,而Promise作为一种高级的异步编程抽象,利用事件循环的特性,提供了清晰、灵活的异步解决方案,二者共同构成了现代JavaScript异步编程的核心基础。

0条评论

您的电子邮件等信息不会被公开,以下所有项均必填

OK! You can skip this field.