Jest 的 Event loop 机制

389 阅读7分钟

本文旨在使用 javascript的事件循环(event loop)机制,抛砖引玉 Jest中的 Timer 事件机制

来聊聊 javascript的事件循环 ( event loop )

首先, js 的事件循环 (之后就简称 event loop) 是 js 的运行机制之一,它是一个基于事件驱动的机制,用于管理未来执行的代码。

js 的 event loop单线程。这是说它在同一个时间只能做一件事情。一心一意

event loop 的核心是事件队列。它是一个规则,制定了不同身份的事件成员,除去先来后到的原则之外,还有 VIP 机制。当一个事件被触发的时候,它就被加入事件队列中,等待 js 引擎去处理。它大概的过程是这样的:

  1. 事件队列中,包含很多的小队,第一小队排队的第一个事件开始
  2. 让这个事件进入大厅,去登记,办事。。。
  3. 如果这个事件中有些大厅办不了,比如:他是个异步VIP,那么它就会被单独邀请去 异步的API 小厅处理。因为他重要,所有之前大厅负责他的工作人员,跟着去陈述客户需求了,所以大厅的这个窗口只有等这个人处理结束
  4. 当VIP 事件处理完成后,继续回到大厅处理属于大厅的业务,直至这个事儿完了
  5. 接下来,再去重新进一个事件

请自行脑补银行窗口排队办业务。这个流程大概就是整个事件循环的过程。

下面举一些js事件循环的插队事件示例:


// 示例代码一:setTimeout

console.log("Start");

setTimeout(function() {

    console.log("Inside setTimeout");

}, 0);

console.log("End");

// 输出结果:Start -> End -> Inside setTimeout


// 示例代码二:Promise

console.log("Start");

let promise = new Promise(function(resolve, reject) {

    console.log("Inside Promise");

    resolve("Promise Resolved");

});

promise.then(function(value) {

    console.log(value);

});

console.log("End");

// 输出结果:Start -> Inside Promise -> End -> Promise Resolved

来聊聊正式内容: Jestevent loop

首先,说说 Jest

Jest 是一个流行的测试框架。它本身提供了许多工具,API帮我们编写测试用例。 这里是 Jest 官网:jestjs.io/docs/gettin…

其次,说说 Timer

Jest内置的一项为 Timer, 它本身提供了 setTimeoutsetIntervalsetImmediate 等 API 来实现异步操作。这些 API 在执行时会调用 JavaScript 的事件循环机制,以便在指定的时间后执行回调函数。

Jest Timer 内置了一个 event loop,它实现了类似于浏览器中的事件循环机制。当使用 Jest Timer API 时,它会将回调函数放入事件队列中,并在指定的时间后执行。与浏览器中的事件循环机制类似,Jest Timer 的事件循环机制也是单线程的,即只能处理一个任务。当 Jest Timer API 调用完毕后,它会将回调函数放入事件队列中,并等待 JavaScript 引擎去处理。

再次,两个事件机制的比对

  • 相似点

    1. 都是单线程
    2. 都需要在事件队列中排队
    3. 都是 js 引擎处理
  • 不同点

    1. 事件循环机制不同:

      • js 是浏览器或者 nodejs引擎的内部机制
      • Jest Timer 是 Jest 框架中的机制,用于测试用例
    2. 回调函数的执行时间不用:

      • js的执行时间,是由事件队列中的排队顺序,以及 js 引擎自身的执行效率决定的
      • Jest Timer 的执行时间可以指定
    3. 其他等

      • js 是用来写代码的
      • jest 是用来测试 js 代码的

举个Jest Timer🌰


// 示例代码:使用 Jest Timer API 模拟异步操作

test("Jest Timer API Test", () => {

    expect.assertions(1);

    return new Promise(resolve => {

        setTimeout(() => {

            expect(1 + 2).toBe(3);

            resolve();

        }, 1000); // 这里设置的时间为 1 秒

    });

});

// 示例代码:使用 Jest Mock API 模拟异步操作

test("Jest Mock API Test", () => {

    const callback = jest.fn();

    setTimeout(() => {

        callback();

    }, 1000); // 这里设置的时间为 1 秒

    jest.advanceTimersByTime(1000); // 快进时间

    expect(callback).toHaveBeenCalled();

});

这里提一下,在 Jest Timer 内置的事件循环机制中,计时器的时间精度是毫秒级别的。如果需要更高的时间精度,可以使用 jest.useFakeTimers() 函数来覆盖 Jest 的默认计时器实现。

一般我们需要精确的断言异步的时间,去做一些断言的时候,会这么做:

    
    // 使用 jest 生命周期 
    beforeEach(() => {
        jest.useFakeTimers();
    });

    afterEach(() => {
        jest.useRealTimers();
        jest.clearAllTimers();
        cleanup();
    });
    
    // 实际断言代码中
    test('it is a demo', async () => {
        // ...
        await act(async () => jest.advanceTimersByTime(6300));
        // ...
    });

从而能在期盼的事件内,断言一些正确的数据表象。

总结一下

总的来说,JavaScript 事件循环机制和 Jest Timer 内置的事件循环机制都是基于事件驱动的机制,用于管理未来执行的代码。它们都是单线程的,需要将事件放入事件队列中,并等待 JavaScript 引擎去处理。虽然它们的实现方式略有不同,但都是为了实现异步操作和提高代码的性能和效率。

其他:有关于事件驱动和机制的一些示例

事件驱动的理念和机制示例


// 示例代码:使用事件监听器监听按钮点击事件

let button = document.querySelector("button");

button.addEventListener("click", function() {

console.log("Button clicked!");

});

在事件驱动的机制中,事件监听器会监听某个事件的发生,并在事件发生时执行相应的回调函数。在示例代码中,我们使用事件监听器 addEventListener 来监听按钮的点击事件,当按钮被点击时,回调函数将被执行。这种事件驱动的机制可以使代码更加简洁和易于理解,同时也能够处理复杂的异步操作。

事件队列中事件的顺序和 JavaScript 引擎的执行效率示例


// 示例代码:演示事件队列中事件的顺序和 JavaScript 引擎的执行效率

console.log("Start");

setTimeout(function() {

    console.log("Inside setTimeout 1");

}, 0);

setTimeout(function() {

    console.log("Inside setTimeout 2");

}, 0);

Promise.resolve().then(function() {

    console.log("Promise resolved");

});

console.log("End");

// 输出结果:Start -> End -> Promise resolved -> Inside setTimeout 1 -> Inside setTimeout 2

在示例代码中,我们使用了 setTimeoutPromise 两种方式来模拟异步操作,并在回调函数和 .then() 方法中输出相应的内容。在代码执行过程中,JavaScript 引擎会先执行同步代码,然后再执行异步代码。而在异步代码中,Promise 对象的 .then() 方法比 setTimeout 的回调函数先被推入事件队列中,所以 Promise 的回调函数会比 setTimeout 的回调函数先被执行。另外,由于 setTimeout 的回调函数的时间都被设置为 0,所以它们会在相同的时间被推入事件队列中,但它们的执行顺序是不确定的,取决于 JavaScript 引擎的执行效率。在这个例子中,我们可以看到,Promise 的回调函数被先执行,然后是 setTimeout 的回调函数。

Node.js 中的事件驱动机制示例


// 示例代码:使用 Node.js 中的事件驱动机制读取文件内容

const fs = require("fs");

let readStream = fs.createReadStream("file.txt");

readStream.on("data", function(data) {

    console.log("Received data: ", data.toString());

});

readStream.on("end", function() {

    console.log("Finished reading file.");

});

在 Node.js 中,事件驱动机制被广泛应用于 I/O 操作等异步操作中。在示例代码中,我们使用 Node.js 内置的 fs 模块创建了一个可读流 readStream,并监听了两个事件 dataend。当 data 事件被触发时,回调函数会输出读取到的数据,而当 end 事件被触发时,回调函数会输出读取文件结束的信息。这种事件驱动的机制使得我们可以使用异步操作来读取大文件,而不会阻塞主线程的执行。

React 中的事件驱动机制示例


// 示例代码:使用 React 中的事件驱动机制监听按钮点击事件

import React from "react";

class Button extends React.Component {

handleClick = () => {

console.log("Button clicked!");

}

render() {

return (

<button onClick={this.handleClick}>Click me</button>

);

}

}

在 React 中,事件驱动机制被应用于组件的交互操作中。在示例代码中,我们创建了一个按钮组件 Button,并在 render() 方法中使用 JSX 语法将其渲染到页面上。在按钮的 onClick 事件中,我们使用箭头函数定义了一个回调函数 handleClick,用于处理按钮被点击时的操作。当按钮被点击时,该回调函数将被执行。这种事件驱动的机制可以使得组件之间的交互更加灵活和高效。

结束了