Node.js事件循环对我来说没有意义

Node.js event loop not making sense to me

本文关键字:有意义 对我来说 循环 js 事件 Node      更新时间:2023-09-26

我是Node.js新手。我一直在努力通过Jim R. Wilson的"Node.js的正确方式",我在书中(以及Node.js本身?)遇到了一个矛盾,我无法通过任何数量的谷歌来调和我的满意。

在书中和我在网上看到的其他资源中反复声明,Node.js逐行运行回调以响应一些事件,直到完成,然后事件循环继续等待或调用下一个回调。因为Node.js是单线程的(并且没有显式地使用集群模块做任何事情,也作为单个进程运行),我的理解是,一次最多只有一个 JavaScript代码块执行。

我理解对了吗?(在我看来)矛盾就在这里。如果是这种情况,Node.js如何具有如此高的并发性?

这是一个直接从书中摘取的例子,说明了我的困惑。它旨在遍历包含数千个XML文件的目录,并将每个文件的相关位提取到JSON文档中。

首先是解析器:

'use strict';
const
  fs = require('fs'),
  cheerio = require('cheerio');
module.exports = function(filename, callback) {
  fs.readFile(filename, function(err, data){
    if (err) { return callback(err); }
    let
      $ = cheerio.load(data.toString()),
      collect = function(index, elem) {
        return $(elem).text();
      };
    callback(null, {
      _id: $('pgterms'':ebook').attr('rdf:about').replace('ebooks/', ''), 
      title: $('dcterms'':title').text(), 
      authors: $('pgterms'':agent pgterms'':name').map(collect), 
      subjects: $('[rdf'':resource$="/LCSH"] ~ rdf'':value').map(collect) 
    });
  });
};

和遍历目录结构的位:

'use strict';
const
  file = require('file'),
  rdfParser = require('./lib/rdf-parser.js');
console.log('beginning directory walk');
file.walk(__dirname + '/cache', function(err, dirPath, dirs, files){
  files.forEach(function(path){
    rdfParser(path, function(err, doc) {
      if (err) {
        throw err;
      } else {
        console.log(doc);
      }
    });
  });
});

如果您运行这段代码,您将得到一个错误,因为程序耗尽了所有可用的文件描述符。这似乎表明程序同时打开了数千个文件。

我的问题是……这怎么可能,除非事件模型和/或并发模型的行为不同于它们被解释的方式?

我相信有人知道这一点,可以阐明它,但目前,我非常困惑!

我理解对了吗?

是的。

如果是这种情况,Node.js是如何高度并发的?

不是javascript执行本身是并发的——IO(和其他繁重的任务)是并发的。当您调用异步函数时,它将启动任务(例如,读取文件)并立即返回"运行脚本的下一行"。然而,该任务将继续在后台(并发地)读取文件,一旦完成,它将把分配给它的回调放到事件循环队列中,该事件循环队列将使用可用数据调用它。

有关"在后台"处理的详细信息,以及node实际上如何管理并行运行所有这些异步任务,请查看问题Nodejs事件循环

这是一个非常简单的描述,省略了很多内容。

files.forEach不是异步的。因此,代码遍历目录中的文件列表,对每个文件调用fs.readFile,然后返回到事件循环。

循环然后有一个文件打开事件加载处理,然后将文件读取事件排队。然后循环可以开始通过并调用回调到fs.readFile与已读取的数据。这些线程一次只能调用一个:正如你所说的,在任何时候只有一个线程在执行javascript。

但是,在调用这些回调函数之前,您已经打开了原始列表中的每个文件,如果文件句柄太多,将导致文件句柄耗尽。

我认为OrangeDog的答案是你具体问题的正确答案。但也许你会发现Philip Roberts的这个简短而精彩的演讲很有帮助,它很好地解释了事件循环的概念和JavaScript的异步处理。注意,该视频与node.js无关,因为这些原则适用于所有JavaScript代码。