繁体   English   中英

如何缠结Node.JS异步

[英]How to wrangle Node.JS async

我正在努力解决如何克服和处理Node.JS的异步特性。 我已经做了很多阅读,并尝试通过使用消息传递解决方案或回调函数使Node达到我想要的目的。

我的问题是我有一个要构造函数加载文件并填充数组的对象。 然后,我希望对该函数的所有调用都使用该加载的数据。 因此,我需要原始调用来等待文件被加载,所有后续调用都需要使用已经加载的私有成员。

我的问题是,即使加载返回数据的函数带有回调,加载数据并获取数据的函数也会异步执行。

无论如何,有什么简单的我想念的吗? 还是我可以在这里使用更简单的模式? 此函数应返回部分已加载文件,但返回未定义。 我已经检查过该文件实际上正在加载,并且可以正常工作。

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();

更新:

这就是我从另一个模块使用歌曲模块的方式

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

“我的问题是,即使返回带有回调的函数,加载数据和获取数据的函数也会异步执行。”

@AlbertoZaccagni我的意思是说这行return this.loadVerses(input, this.getVerse); 当我希望文件等待回调时,在文件加载之前返回。

这就是节点的工作方式,我将尝试通过一个例子来阐明它。

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');

您正在读取文件,并且在控制台中可能会有

  • 开始
  • 即将阅读...
  • 结束
  • ...读!

您可以单独尝试一下以查看实际效果,并对其进行调整以了解要点。 在这里需要注意的重要一点是,您的代码将继续运行,跳过回调的执行,直到读取文件为止。

仅仅因为您声明了回调并不意味着执行将暂停,直到调用该回调然后继续执行。

所以这就是我要更改代码的方式:

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();

要使用它:

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

我忽略了

  1. 我们没有将错误视为回调的第一个参数的事实
  2. 足够快地打电话会创造赛车条件的事实,但是我相信这是另一个问题

[汇总为答案,并根据我之前的评论进行了扩展]

TL; DR您需要对代码进行结构化,以便任何操作的结果只能在该操作的回调中使用,因为您无法在其他任何地方访问它。

虽然将其分配给外部全局变量当然可以按预期工作,但是只有在回调触发后才会发生,这是在您无法预测的时间发生的。

评论

回调不返回值,因为就其本质而言,它们将在将来的某个时间执行。

一旦将回调函数传递到控制异步函数中,则在周围函数决定调用它时将执行该回调函数。 您无法控制它,因此等待返回结果将不起作用。

您的示例代码song.verse(1); 不能期望它返回任何有用的东西,因为它会被立即调用,并且由于尚未触发回调,因此将仅返回它可以的唯一值: null

恐怕这种对带有传递的回调的异步函数的依赖是NodeJS不可操作的功能。 它是它的核心

不过不要灰心。 对这里所有NodeJS问题的快速调查非常清楚地表明,这种只能在其回调中使用异步操作的结果的想法,是任何了解如何在NodeJS中编程的人的最大障碍。

有关正确构造NodeJS代码的各种方法的真正出色的解释/教程,请参阅《使用承诺,生成器和其他方法来管理Node.js回调地狱》

我相信它清楚,简洁地描述了您面临的问题,并提供了几种正确重构代码的方法。

此处提到的两个功能,即Promises和Generators,是编程功能/概念,我认为对您的理解很有用。

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 */

});

实际上,Promises只是以更线性的方式封送标准回调金字塔的一种手段。 (我个人认为他们需要一个新名字,因为“将来可能会出现的某些价值的承诺”的想法不是一个容易缠住的人,至少对我而言不是。)

承诺通过(在幕后)重组“回调地狱”来做到这一点:

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

变得更类似于:

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

p();

您提供给resolve()任何值将成为下一个“ then-able”回调的唯一参数,并且任何错误都通过rejected()传递,因此任何.catch(function(err){ ... })您定义的处理程序。

Promise还可以执行(某种标准) async模块所期望的所有操作,例如串行或并行运行回调以及对数组的元素进行操作,一旦收集了所有结果,便将其收集的结果返回给回调。

但是您会注意到Promises并没有完全按照您想要的去做,因为所有内容仍然在回调中。

(请参阅bluebird ,了解我认为是最简单,因此也是最好的Promises软件包,需要首先学习。)

(请注意, fs.readFileAsync是不是一个错字。的一个非常有用的功能bluebird是,它可以进行添加这个和其他基于承诺的版本fs的现有功能,以标准的fs对象,也懂得如何“承诺”其他模块,例如requestmkdirp )。

Generators是上面教程中描述的其他功能,但是仅在新的,更新的但尚未正式发布的JavaScript版本(代号为“ Harmony”)中可用。

使用generators还可以使您以更线性的方式编写代码,因为generators提供的功能之一就是能够以不会对JavaScript事件循环造成严重破坏的方式等待异步操作的结果。 (但是正如我所说,它还不是通用功能。)

但是,您可以根据需要在当前版本的node中使用生成器,只需在节点命令行中添加“ --harmony”即可告诉它打开下一版本JavaScript的最新功能。

暂无
暂无

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

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