Eventloop

本文深入探讨了JavaScript的非阻塞单线程特性,解释了事件循环和任务队列的工作原理,包括宏任务与微任务的执行顺序。通过示例代码解析了setTimeout、Promise和async/await在事件循环中的行为,阐述了JavaScript如何处理异步操作,以及任务队列对代码执行的影响。

众所周知JS是门非阻塞单线程语言,因为最初JS就是为了和浏览器交互而诞生的。如果JS是门多线程的语言的话,我们在多个线程中处理DOM就可能会发生问题

JS在执行的过程中会产生执行环境,这些执行环境会被顺序的加入到执行栈中。如果遇到异步的代码,会被挂起并加入的Task队列中。一旦执行栈为空,Event Loop就会从Task队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说JS中的异步还是同步行为

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
},0);

console.log('script end');

以上代码虽然setTimeout延时为0,其实还是异步。这是因为HTML5标准规定这个函数第二个参数不得小于4毫秒,不足会自动增加。所以setTimeout还是会在script end 之后打印。

不同的任务源会被分配到不同的Task队列中,任务源可以分为微任务(microtask)和宏任务(macrotask)。在ES6规范中,microtask成为jobs,macrortask称为 task。

 

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
},0);

new Promise((resolve) => {
    console.log('Promise')
    resolve()
}).then(function() {
    console.log('promise1');
}).then(function() {
    console.log('promise2');
});

console.log('script end');

最后输出的结果 

script start => promise => script end => promise1 => promise2 => setTimeout

宏任务和微任务的API 

  • macrotaskssetTimeoutsetIntervalsetImmediate, I/O, UI rendering
  • microtasksprocess.nextTickPromisesObject.observe(废弃), MutationObserver

在每一次事件循环中,macrotask 只会提取一个执行,而 microtask 会一直提取,直到 microtasks 队列清空

这份代码中应该是先整个任务,把整个任务加入到macrotask。执行同步代码,script start , promise, script end, 然后读取微任务队列,将回调函数写入对应的队列中,等待主任务执行完毕后,才会一个一个执行microtask,本轮的任务就执行完毕了,之后再执行下一轮event loop,从宏任务开始,setTimeout输出。

下面提供一个例题,网上找的

                 async function async1() {           
                     console.log("async1 start");  //(2)        
                     await  async2();            
                     console.log("async1 end");   //(6)    
                 }        
                 async  function async2() {          
                     console.log( 'async2');   //(3)     
                 }       
                 console.log("script start");  //(1)      
                 setTimeout(function () {            
                     console.log("settimeout");  //(8)      
                 },0);        
                 async1();        
                 new Promise(function (resolve) {           
                     console.log("promise1");   //(4)         
                     resolve();        
                 }).then(function () {            
                     console.log("promise2");    //(7)    
                 });        
                 console.log('script end');//(5)

解析如下:

先按顺序执行同步代码  从‘script start‘开始,

执行到setTimeout函数时,将其回调函数加入队列(此队列与promise队列不是同一个队列,执行的优先级低于promise)

然后调用async1()方法,await async2();//执行这一句后,输出async2后,await会让出当前线程,将后面的代码加到任务队列中,然后继续执行test()函数后面的同步代码

继续执行创建promise对象里面的代码属于同步代码,promise的异步性体现在then与catch处,所以promise1被输出,然后将then函数的代码加入队列,继续执行同步代码,输出script end。至此同步代码执行完毕。

开始从队列中调取任务执行,由于刚刚提到过,setTimeout的任务队列优先级低于promise队列,所以首先执行promise队列的第一个任务,因为在async函数中有await表达式,会使async函数暂停执行,等待表达式中的 Promise 解析完成后继续执行 async 函数并返回解决结果。

所以先执行then方法的部分,输出promise2,然后执行async1中await后面的代码,输出async1 end。。最后promise队列中任务执行完毕,再执行setTimeout的任务队列,输出settimeout。
 

setTimeout(fn,0)的含义是指某个任务在主线程最早可得的空闲时间执行。它在“任务队列”的尾部添加一个事件,因此要等到同步任务和“任务队列”现有的时间处理完才会得到执行。

 

按照事件循环机制分析以上代码运行流程:

  1. 首先,事件循环从宏任务(macrotask)队列开始,这个时候,宏任务队列中,只有一个script(整体代码)任务;当遇到任务源(task source)时,则会先分发任务到对应的任务队列中去。

  2. 然后我们看到首先定义了两个async函数,接着往下看,然后遇到了 `console` 语句,直接输出 `script start`。输出之后,script 任务继续往下执行,遇到 `setTimeout`,其作为一个宏任务源,则会先将其任务分发到对应的队列中。

  3. script 任务继续往下执行,执行了async1()函数,前面讲过async函数中在await之前的代码是立即执行的,所以会立即输出`async1 start`。
遇到了await时,会将await后面的表达式执行一遍,所以就紧接着输出`async2`,然后将await后面的代码也就是`console.log('async1 end')`加入到microtask中的Promise队列中,接着跳出async1函数来执行后面的代码。

  4. script任务继续往下执行,遇到Promise实例。由于Promise中的函数是立即执行的,而后续的 `.then` 则会被分发到 microtask 的 `Promise` 队列中去。所以会先输出 `promise1`,然后执行 `resolve`,将 `promise2` 分配到对应队列。

  5. script任务继续往下执行,最后只有一句输出了 `script end`,至此,全局任务就执行完毕了。
根据上述,每次执行完一个宏任务之后,会去检查是否存在 Microtasks;如果有,则执行 Microtasks 直至清空 Microtask Queue。
因而在script任务执行完毕之后,开始查找清空微任务队列。此时,微任务中, `Promise` 队列有的两个任务`async1 end`和`promise2`,因此按先后顺序输出 `async1 end,promise2`。当所有的 Microtasks 执行完毕之后,表示第一轮的循环就结束了。

  6. 第二轮循环依旧从宏任务队列开始。此时宏任务中只有一个 `setTimeout`,取出直接输出即可,至此整个流程结束。

 

 

 

An event loop is a programming construct that allows for the handling of asynchronous operations in a program. It works by waiting for and dispatching events or messages in a program. Specifically, an event loop listens for events, such as user input or network requests, and then calls the appropriate callback function to handle the event[^1]. In the context of programming, especially in languages that support asynchronous programming like Python, the event loop is crucial for managing multiple operations without blocking the execution of the program. When an event occurs, the event loop picks it up and executes the associated handler. This is particularly useful in applications that require high concurrency, such as web servers or GUI applications, where the program must respond to various inputs and events from users or external systems[^1]. For example, in a web browser, the event loop handles user interactions like clicks and keystrokes, as well as rendering updates and network requests. Similarly, in a server-side application, the event loop can manage multiple client connections and process their requests efficiently without the need for multi-threading. The event loop operates on a single thread, using non-blocking I/O calls, which means that it can handle many simultaneous connections and operations without the overhead of thread management. This model is often more efficient and simpler to manage than traditional multi-threaded approaches, which can suffer from issues such as race conditions and deadlocks[^1]. To illustrate, consider a simple server that needs to handle multiple client requests. Instead of creating a new thread for each client, which can be resource-intensive and complex to manage, the server can use an event loop to handle each request asynchronously. When a client sends a request, the event loop schedules the request to be processed, and once the processing is complete, the result is sent back to the client. In summary, the event loop is a fundamental concept in asynchronous programming, enabling programs to handle multiple tasks concurrently and efficiently, making it possible to build scalable and responsive applications. ```python import asyncio async def handle_client(reader, writer): data = await reader.read(100) message = data.decode() addr = writer.get_extra_info('peername') print(f"Received {message} from {addr}") print("Send: %r" % message) writer.write(data) await writer.drain() print("Close the client socket") writer.close() async def main(): server = await asyncio.start_server( handle_client, '127.0.0.1', 8888) addr = server.sockets[0].getsockname() print(f'Serving on {addr}') async with server: await server.serve_forever() asyncio.run(main()) ``` The code above demonstrates a simple asynchronous server using Python's `asyncio` library, which utilizes an event loop to handle multiple client connections concurrently.
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值