简体   繁体   English

如何缠结Node.JS异步

[英]How to wrangle Node.JS async

I am struggling with getting my head around how to overcome and handle the async nature of Node.JS. 我正在努力解决如何克服和处理Node.JS的异步特性。 I have done quite a bit of reading on it and tried to make Node do what I want by either using a message passing solution or callback functions. 我已经做了很多阅读,并尝试通过使用消息传递解决方案或回调函数使Node达到我想要的目的。

My problem is I have a object where I want to constructor to load a file and populate an array. 我的问题是我有一个要构造函数加载文件并填充数组的对象。 Then I want all calls to this function use that loaded data. 然后,我希望对该函数的所有调用都使用该加载的数据。 So I need the original call to wait for the file to be loaded and all subsequent calls to use the already loaded private member. 因此,我需要原始调用来等待文件被加载,所有后续调用都需要使用已经加载的私有成员。

My issue is that the function to load load the data and get the data is being executed async even if it return a function with a callback. 我的问题是,即使加载返回数据的函数带有回调,加载数据并获取数据的函数也会异步执行。

Anyways, is there something simple I am missing? 无论如何,有什么简单的我想念的吗? Or is there an easier pattern I could use here? 还是我可以在这里使用更简单的模式? This function should return part of the loaded file but returns undefined. 此函数应返回部分已加载文件,但返回未定义。 I have checked that the file is actually being loaded, and works correctly. 我已经检查过该文件实际上正在加载,并且可以正常工作。

function Song() {
    this.verses = undefined;

    this.loadVerses = function(verseNum, callback) {
        if (this.verses === undefined) {
            var fs = require('fs'),
                filename = 'README.md';

            fs.readFile(filename, 'utf8', function(err, data) {
                if (err) {
                    console.log('error throw opening file: %s, err: %s', filename, err);
                    throw err;
                } else {
                    this.verses = data;
                    return callback(verseNum);
                }
            });
        } else {
            return callback(verseNum);
        }
    }

    this.getVerse = function(verseNum) {
        return this.verses[verseNum + 1];
    }
}

Song.prototype = {
    verse: function(input) {
        return this.loadVerses(input, this.getVerse);
    }
}

module.exports = new Song();

Update: 更新:

This is how I am using the song module from another module 这就是我从另一个模块使用歌曲模块的方式

var song = require('./song');
return song.verse(1);

"My issue is that the function to load the data and get the data is being executed async even if it return a function with a callback." “我的问题是,即使返回带有回调的函数,加载数据和获取数据的函数也会异步执行。”

@AlbertoZaccagni what I mean by that scentence is that this line return this.loadVerses(input, this.getVerse); @AlbertoZaccagni我的意思是说这行return this.loadVerses(input, this.getVerse); returns before the file is loaded when I expect it to wait for the callback. 当我希望文件等待回调时,在文件加载之前返回。

That is how node works, I will try to clarify it with an example. 这就是节点的工作方式,我将尝试通过一个例子来阐明它。

function readFile(path, callback) {
  console.log('about to read...');
  fs.readFile(path, 'utf8', function(err, data) {
    callback();
  });
}

console.log('start');
readFile('/path/to/the/file', function() {
  console.log('...read!');
});
console.log('end');

You are reading a file and in the console you will likely have 您正在读取文件,并且在控制台中可能会有

  • start 开始
  • about to read... 即将阅读...
  • end 结束
  • ...read! ...读!

You can try that separately to see it in action and tweak it to understand the point. 您可以单独尝试一下以查看实际效果,并对其进行调整以了解要点。 What's important to notice here is that your code will keep on running skipping the execution of the callback, until the file is read. 在这里需要注意的重要一点是,您的代码将继续运行,跳过回调的执行,直到读取文件为止。

Just because you declared a callback does not mean that the execution will halt until the callback is called and then resumed. 仅仅因为您声明了回调并不意味着执行将暂停,直到调用该回调然后继续执行。

So this is how I would change that code: 所以这就是我要更改代码的方式:

function Song() {
  this.verses = undefined;

  this.loadVerses = function(verseNum, callback) {
    if (this.verses === undefined) {
      var fs = require('fs'),
      filename = 'README.md';
      fs.readFile(filename, 'utf8', function(err, data) {
        if (err) {
          console.log('error throw opening file: %s, err: %s', filename, err);
          throw err;
        } else {
          this.verses = data;
          return callback(verseNum);
        }
      });
    } else {
      return callback(verseNum);
    }
  }
}

Song.prototype = {
  verse: function(input, callback) {
    // I've removed returns here
    // I think they were confusing you, feel free to add them back in
    // but they are not actually returning your value, which is instead an
    // argument of the callback function
    this.loadVerses(input, function(verseNum) {
      callback(this.verses[verseNum + 1]);
    });
  }
}

module.exports = new Song();

To use it: 要使用它:

var song = require('./song');
song.verse(1, function(verse) {
  console.log(verse);
});

I've ignored 我忽略了

  1. the fact that we're not treating the error as first argument of the callback 我们没有将错误视为回调的第一个参数的事实
  2. the fact that calling this fast enough will create racing conditions, but I believe this is another question 足够快地打电话会创造赛车条件的事实,但是我相信这是另一个问题

[Collected into an answer and expanded from my previous comments] [汇总为答案,并根据我之前的评论进行了扩展]

TL;DR You need to structure your code such that the result of any operation is only used inside that operation's callback, since you do not have access to it anywhere else. TL; DR您需要对代码进行结构化,以便任何操作的结果只能在该操作的回调中使用,因为您无法在其他任何地方访问它。

And while assigning it to an external global variable will certainly work as expected, do so will only occur after the callback has fired, which happens at a time you cannot predict . 虽然将其分配给外部全局变量当然可以按预期工作,但是只有在回调触发后才会发生,这是在您无法预测的时间发生的。

Commentary 评论

Callbacks do not return values because by their very nature, they are executed sometime in the future. 回调不返回值,因为就其本质而言,它们将在将来的某个时间执行。

Once you pass a callback function into a controlling asynchronous function, it will be executed when the surrounding function decides to call it. 一旦将回调函数传递到控制异步函数中,则在周围函数决定调用它时将执行该回调函数。 You do not control this, and so waiting for a returned result won't work. 您无法控制它,因此等待返回结果将不起作用。

Your example code, song.verse(1); 您的示例代码song.verse(1); cannot be expected to return anything useful because it is called immediately and since the callback hasn't yet fired, will simply return the only value it can: null . 不能期望它返回任何有用的东西,因为它会被立即调用,并且由于尚未触发回调,因此将仅返回它可以的唯一值: null

I'm afraid this reliance on asynchronous functions with passed callbacks is an irremovable feature of how NodeJS operates; 恐怕这种对带有传递的回调的异步函数的依赖是NodeJS不可操作的功能。 it is at the very core of it . 它是它的核心

Don't be disheartened though. 不过不要灰心。 A quick survey of all the NodeJS questions here shows quite clearly that this idea that one must work with the results of async operations only in their callbacks is the single greatest impediment to anyone understanding how to program in NodeJS. 对这里所有NodeJS问题的快速调查非常清楚地表明,这种只能在其回调中使用异步操作的结果的想法,是任何了解如何在NodeJS中编程的人的最大障碍。

For a truly excellent explanation/tutorial on the various ways to correctly structure NodeJS code, see Managing Node.js Callback Hell with Promises, Generators and Other Approaches . 有关正确构造NodeJS代码的各种方法的真正出色的解释/教程,请参阅《使用承诺,生成器和其他方法来管理Node.js回调地狱》

I believe it clearly and succinctly describes the problem you face and provides several ways to refactor your code correctly. 我相信它清楚,简洁地描述了您面临的问题,并提供了几种正确重构代码的方法。

Two of the features mentioned there, Promises and Generators, are programming features/concepts, the understanding of which would I believe be of great use to you. 此处提到的两个功能,即Promises和Generators,是编程功能/概念,我认为对您的理解很有用。

Promises (or as some call them, Futures ) is/are a programming abstraction that allows one to write code a little more linearly in a if this then that style, like Promises (或一些给他们打电话, Futures )是/是一个编程抽象,允许一个线性写代码多一点的if this then that风格,像

fs.readFileAsync(path).then(function(data){ 

    /* do something with data here */
    return result;

}).catch(function(err){

    /* deal with errors from readFileAsync here */

}).then(function(result_of_last_operation){ 

    /* do something with result_of_last_operation here */

    if(there_is_a_problem) throw new Error('there is a problem');

    return final_result;

})
.catch(function(err){

    /* deal with errors when there_is_a_problem here */

}).done(function(final_result){

    /* do something with the final result */

});

In reality, Promises are simply a means of marshaling the standard callback pyramid in a more linear fashion. 实际上,Promises只是以更线性的方式封送标准回调金字塔的一种手段。 (Personally I believe they need a new name, since the idea of "a promise of some value that might appear in the future" is not an easy one to wrap one's head around, at least it wasn't for me.) (我个人认为他们需要一个新名字,因为“将来可能会出现的某些价值的承诺”的想法不是一个容易缠住的人,至少对我而言不是。)

Promises do this by (behind the scenes) restructuring "callback hell" such that: 承诺通过(在幕后)重组“回调地狱”来做到这一点:

asyncFunc(args,function callback(err,result){
   if(err) throw err;
   /* do something with the result here*/
});

becomes something more akin to: 变得更类似于:

var p=function(){ 
    return new Promise(function(resolve,reject){
        asyncFunc(args,function callback(err,result){
            if(err) reject(err)
            resolve(result);
        });
    });
});

p();

where any value you provide to resolve() becomes the only argument to the next "then-able" callback and any error is passed via rejected() , so it can be caught by any .catch(function(err){ ... }) handlers you define. 您提供给resolve()任何值将成为下一个“ then-able”回调的唯一参数,并且任何错误都通过rejected()传递,因此任何.catch(function(err){ ... })您定义的处理程序。

Promises also do all the things you'd expect from the (somewhat standard) async module, like running callbacks in series or in parallel and operating over the elements of an array, returning their collected results to a callback once all the results have been gathered. Promise还可以执行(某种标准) async模块所期望的所有操作,例如串行或并行运行回调以及对数组的元素进行操作,一旦收集了所有结果,便将其收集的结果返回给回调。

But you will note that Promises don't quite do what you want, because everything is still in callbacks. 但是您会注意到Promises并没有完全按照您想要的去做,因为所有内容仍然在回调中。

(See bluebird for what I believe is the simplest and thus, best Promises package to learn first.) (请参阅bluebird ,了解我认为是最简单,因此也是最好的Promises软件包,需要首先学习。)

(And note that fs.readFileAsync is not a typo. One useful feature of bluebird is that it can be made to add this and other Promises-based versions of fs 's existing functions to the standard fs object. It also understands how to "promisify" other modules such as request and mkdirp ). (请注意, fs.readFileAsync是不是一个错字。的一个非常有用的功能bluebird是,它可以进行添加这个和其他基于承诺的版本fs的现有功能,以标准的fs对象,也懂得如何“承诺”其他模块,例如requestmkdirp )。

Generators are the other feature described in the tutorial above, but are only available in the new, updated but not yet officially released version of JavaScript (codenamed "Harmony"). Generators是上面教程中描述的其他功能,但是仅在新的,更新的但尚未正式发布的JavaScript版本(代号为“ Harmony”)中可用。

Using generators would also allow you to write code in a more linear manner, since one of the features it provides is the ability of waiting on the results of an asynchronous operation in a way that doesn't wreak havoc with the JavaScript event-loop. 使用generators还可以使您以更线性的方式编写代码,因为generators提供的功能之一就是能够以不会对JavaScript事件循环造成严重破坏的方式等待异步操作的结果。 (But as I said, it's not a feature in general use yet.) (但是正如我所说,它还不是通用功能。)

You can however use generators in the current release of node if you'd like, simply add "--harmony" to the node command line to tell it to turn on the newest features of the next version of JavaScript. 但是,您可以根据需要在当前版本的node中使用生成器,只需在节点命令行中添加“ --harmony”即可告诉它打开下一版本JavaScript的最新功能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM