簡體   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