使用异步Node.JS来提供HTTP请求服务。

6
我已经成功编写了一些nodejs HTTP处理程序,以响应HTTP请求中的数据。然而,我编写的所有内容都是使用函数的*Sync版本。现在,我很快就会遇到这种方法的局限性。
然而,我无法正确地在HTTP请求上下文中使用异步函数。如果我尝试异步调用,处理过程会迅速结束并返回,没有给代码处理数据的机会。
正确的方法是什么?我找不到任何好的例子,所以任何指向文献的指针都是受欢迎的。除此之外,有一个扫描本地目录的GET请求处理程序的示例,并返回文件名和相应行数的json列表(或者任何上述的存根代码,显示适当的技术)吗?

你必须在异步函数的回调中完成所有工作。 - Explosion Pills
我知道这个。如何确保请求处理程序在所有回调完成之前不返回(不会掉落)。 - Dmitry B.
有很多不同的方法来实现这个。我建议使用ES7转译+异步/等待。如果你不想麻烦,那么co/yield就非常接近了。我仍然建议使用“Promises”(https://github.com/petkaantonov/bluebird),这些“Promises”已经内置到node中,但我链接的蓝鸟库可以将不支持“Promises”的库变成“Promises”。我强烈推荐“Promises”……我认为它们大大简化了异步流程。[async](https://github.com/caolan/async)也很受欢迎。最后,你可以自己打造实现方式。 - Explosion Pills
请展示你的代码,以显示你正在尝试做什么。这里并不是什么秘密,答案就是在异步回调中继续下一步操作,而不是在异步调用后的下一行代码中进行。 - jfriend00
请见本文中“创建简单静态文件服务器”一节中使用异步I/O的简单node.js服务器示例:http://www.hongkiat.com/blog/node-js-server-side-javascript/。 - jfriend00
2个回答

4

这是一个简单的示例:

var http = require('http')
var fs = require('fs')

function dir (req, res) {
  fs.readdir('.', function (error, files) {
    if (error) {
      res.writeHead(500)
      res.end(error.message)
      return
    }
    files.forEach(function (file) {
      res.write(file + '\n')
    })
    res.end()
  })
}

var server = http.createServer(dir)
server.listen(7000)

使用node server.js命令运行它,并使用curl :7000进行测试。

是的,请求处理程序在readdir回调执行之前返回。这是按设计进行的。这就是异步编程的工作方式。没关系,当文件系统IO完成时,回调将被执行并发送响应。


好的,这很有道理。但是,我在将其扩展到具有嵌套级别的异步处理程序的更复杂情况时,例如读取每个文件的内容以计算行数和/或对多个(子)目录执行此操作时,确实遇到了精神障碍。 - Dmitry B.
异步控制流需要从同步代码进行大量调整。尝试使用nodeschool.io教程、callbackhell.com、阅读GitHub上的开源项目等方法。最终你会有所领悟。 - Peter Lyons

2

Peter Lyons的回答很好/正确。我想进一步扩展它,并建议使用promise和co以及嵌套/循环异步性来实现同步。

/* Script to count all lines of a file */
const co = require("co");
// Promisifed fs -- eventually node will support this on its own
const fs = require("mz/fs");
const rootDir = 'files/';

// Recursivey count the lines of all files in the given directory and sum them
function countLines(directory) {
    // We can only use `yield` inside a generator block
    // `co` allows us to do this and walks through the generator on its own
    // `yield` will not move to the next line until the promise resolves
    //
    // This is still asynchronous code but it is written in a way
    // that makes it look synchronized. This entire block is asynchronous, so we
    // can `countLines` of multiple directories simultaneously
    return co(function* () {
        // `files` will be an array of files in the given directory
        const files = yield fs.readdir(directory);

        // `.map` will create an array of promises. `yield` only completes when
        // *all* promises in the array have resolved
        const lines = yield files.map(file => countFileLines(file, directory));

        // Sum the lines of all files in this directory
        return lines.reduce((a, b) => a + b, 0);
    });
}

function countFileLines(file, directory) {
    // We need the full path to read the file
    const fullPath = `${directory}/${file}`;

    // `co` returns a promise, so `co` itself can be yielded
    // This entire block is asynchronous so we should be able to count lines
    // of files without waiting for each file to be read
    return co(function* () {
        // Used to check whether this file is a directory
        const stats = yield fs.stat(fullPath);
        if (stats.isDirectory()) {
            // If it is, recursively count lines of this directory
            return countLines(fullPath);
        }
        // Otherwise just get the line count of the file
        const contents = yield fs.readFile(fullPath, "utf8");
        return contents.split("\n").length - 1;
    });
}

co(function* () {
  console.log(yield countLines(rootDir));
})
// All errors propagate here
.catch(err => console.error(err.stack));

请注意,这只是一个例子。可能已经有用于计算目录中文件行数的库,而且肯定有简化递归读取/全局匹配文件的库。

1
天哪!我已经断断续续地使用 JavaScript 很长一段时间了,基本上是从它的开始,但看起来在最近这段时间里,这种语言经历了一次很棒的变异。 - Dmitry B.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接