2016-05-28 8 views
1

私は2つのリストを結合しようとしています。 List1には681のフランス語動詞があり、List2には681の翻訳があります。 私はjavascriptとnode.jsを使ってファイルを読み込んでいます。 は、ここに私の試みです:readFileが非同期であるためプログラムが失敗しますか?

var frenchWords, englishWords, combinedList; 
    fs = require('fs') 

// 1. read the french file 

    fs.readFile('frenchVerbsList.txt', 'utf8', function (err,data) { 
    if (err) { 
     return console.log("ERROR here!: " + err); 
    } 
    frenchWords = data.split('\n'); 
    }); 

//read the english file 

    fs.readFile('englishVerbsList.txt', 'utf8', function (err,data2) { 
    if (err) { 
     return console.log("ERROR here!: " + err); 
    } 
    englishWords = data2.split('\n'); 
    }); 

// 2. combine the lists 
//*** it fails here, I'm guessing it's because the readFile operation hasn't finished yet. 

    var combinedList; 
    for(i=0; i<frenchWords.length; i++){ 
     combinedList[i] = frenchWords[i] + ",,," + englishWords[i]; 
    } 
// 3. check the result 

    for(i=0; i<10; i++){ 
     console.log(combinedList[i]); 
    } 
任意の助けのために非常に多くの

おかげで、私は、async、例えば、いくつかのライブラリを使用してください:-)をアクティブ(両方のタスクを待つことを

+0

あなたのループがfs.readFileコールのコールバックの前に実行されています時間がかかります)* frenchWords *と* englishWords *はそこでは定義されていません。関数の非同期バージョンを使用すると、インタープリタはこれらの関数の結果を「待機」せずに次の行(この場合はループ)を解析し、変数に値を割り当てるコールバックが呼び出されたことを確認できません彼らが最初に呼び出される可能性があります!) –

答えて

1

あなたは正しいですfs.readFile()コールバックの非同期性が原因で問題が発生している可能性があります。

これらのコールバックは、残りのコードが引き続き実行されている間、未定義の時刻に呼び出されます。イベント駆動型のnode.js設計のため、残りのコードが実行されるまでコールバックは呼び出されません。したがって、結果が出る前にenglishWordsfrenchWords変数を使用しようとしていることが保証されています。

あなたは、さまざまなオプションの束を持っている:あなたはfs.readFileSync()の使用に切り替えることができます(ほとんどの場合にはお勧めしません)fs.readFileAsyncへ

スイッチ

。現在の制御の流れが働くので、これは最も簡単な変更です。しかし、これは一般にnode.jsの開発にはお勧めできません。これは、サーバの使用に非効率的なためです。このコードが、ファイルが読み込まれるのを待っている間に他のことを行う能力を維持できる/できなければならないサーバープロセスにあった場合、fs.readFileSync()はスケーラビリティを犠牲にします。これが単なるスクリプト(ロードされたサーバではない)であれば、fs.readFileSync()はうまくいくかもしれません。しかし、おそらく、非同期操作で適切にコーディングするより良い "node.jsスタイル"の待機を学ぶべきです(次のオプションを参照してください)。

継続フローインサイドによるSerializeを操作コールバック

あなたは、ネストを使用して非同期操作をシリアル化することができます。これには、非同期コールバック内でのみ処理ロジックを継続する必要があります。そうすることで、必要な結果が処理を継続できるようになることがわかります。

const fs = require('fs') 

// read the french file 
fs.readFile('frenchVerbsList.txt', 'utf8', function (err, data) { 
    if (err) { 
     return console.log("ERROR here!: " + err); 
    } 
    var frenchWords = data.split('\n'); 

    //read the english file 
    fs.readFile('englishVerbsList.txt', 'utf8', function (err, data2) { 
     if (err) { 
      return console.log("ERROR here!: " + err); 
     } 
     var englishWords = data2.split('\n'); 

     // 2. combine the lists 
     var combinedList = []; 
     for (i = 0; i < frenchWords.length; i++) { 
      combinedList[i] = frenchWords[i] + ",,," + englishWords[i]; 
     } 

     // 3. check the result 
     for (i = 0; i < 10; i++) { 
      console.log(combinedList[i]); 
     } 
    }); 
}); 

手動でコード、それは前に行われた最初の非同期操作を待機することで上記のどちらの非同期操作が完了している

シリアル化オプションは欠点を持っているためのチェック:それはこのようになります。次の非同期操作を開始します。両方の非同期操作が並行して実行される可能性があるため(理想的ではありません)、最終結果が速くなります。以下のオプションはすべて、非同期操作を並行して実行する方法と、両方が終了したときに監視する方法で、最終処理をトリガーすることができます。これは手動監視オプションです。フランス語と英語の両方の読み込みの完了コールバックで、もう一方が完了しているかどうかを確認します。そうであれば、結果を処理する関数を呼び出します。唯一以来1にはエラーがない限り、そのうちの一つが第二完了します、一度に完了することができ、結果を処理するために、あなたの関数を呼び出します:

var frenchWords, englishWords; 
fs = require('fs') 

// read the french file 
fs.readFile('frenchVerbsList.txt', 'utf8', function (err, data) { 
    if (err) { 
     return console.log("ERROR here!: " + err); 
    } 
    frenchWords = data.split('\n'); 
    if (frenchWords && englishWords) { 
     processResults(); 
    } 
}); 

//read the english file 

fs.readFile('englishVerbsList.txt', 'utf8', function (err, data2) { 
    if (err) { 
     return console.log("ERROR here!: " + err); 
    } 
    englishWords = data2.split('\n'); 
    if (frenchWords && englishWords) { 
     processResults(); 
    } 
}); 

function processResults() { 

    // combine the lists 
    var combinedList = []; 
    for (let i = 0; i < frenchWords.length; i++) { 
     combinedList[i] = frenchWords[i] + ",,," + englishWords[i]; 
    } 

    // check the result 
    for (let i = 0; i < 10; i++) { 
     console.log(combinedList[i]); 
    } 
} 

使用ES6があなたの非同期操作を監視することを約束し

ES6では、約束がJavascript仕様の標準部分になり、複数の非同期操作を調整する優れた方法であり、適切なエラー処理(特に複雑な状況で)をより簡単に行うことができます。ここで約束を使用するには、まずfs.readFile()の「約束された」バージョンを作成したいと考えています。これは、プレーンコールバックの代わりに約束を使用するラッパー関数です。次に、Promise.all()を使用して、2つの非同期操作がいつ実行されるかを調整できます。

var fs = require('fs'); 
// promise wrapper 
fs.readFileAsync = function(file, encoding) { 
    return new Promise(function(resolve, reject) { 
     fs.readFile(file, encoding, function(err, data) { 
      if (err) return reject(err); 
      resolve(data); 
     });   
    }); 
} 

// common helper function 
function readFileSplitWords(file) { 
    return fs.readFileAsync(file, 'utf8').then(function(data) { 
     // make split words be the fulfilled value of the promise 
     return data.split('\n'); 
    }); 
} 

var frenchPromise = readFileSplitWords('frenchVerbsList.text'); 
var englishPromise = readFileSplitWords('englishVerbsList.txt'); 
Promise.all([frenchPromise, englishPromise]).then(function(results) { 
    // combine the lists 
    var frenchWords = results[0], englishWords = results[1]; 
    var combinedList = []; 
    for (i = 0; i < frenchWords.length; i++) { 
     combinedList[i] = frenchWords[i] + ",,," + englishWords[i]; 
    } 

    // check the result 
    for (i = 0; i < 10; i++) { 
     console.log(combinedList[i]); 
    } 
}, function(err) { 
    // handle an error here 
}); 

ES6約束は非常に可能な拡張約束機能の約束ライブラリを使用しますが、一部のサードパーティのライブラリを追加した約束を使用して非常に便利な機能があります。私は個人的にBluebirdライブラリを使用しています。ここでは、前のオプションは、ブルーバードライブラリを使用してどのように見えるかです:

const Promise = require('bluebird'); 
const fs = Promise.promisifyAll(require('fs')); 

// common helper function 
function readFileSplitWords(file) { 
    return fs.readFileAsync(file, 'utf8').then(function(data) { 
     // make split words be the fulfilled value of the promise 
     return data.split('\n'); 
    }); 
} 

var frenchPromise = readFileSplitWords('frenchVerbsList.text'); 
var englishPromise = readFileSplitWords('englishVerbsList.txt'); 
Promise.all([frenchPromise, englishPromise]).spread(function(frenchWords, englishWords) { 
    // combine the lists 
    var combinedList = []; 
    for (i = 0; i < frenchWords.length; i++) { 
     combinedList[i] = frenchWords[i] + ",,," + englishWords[i]; 
    } 

    // check the result 
    for (i = 0; i < 10; i++) { 
     console.log(combinedList[i]); 
    } 
}, function(err) { 
    // handle an error here 
}); 

これは、ブルーバードのPromise.promisifyAll()が自動的fsライブラリ(非常に便利)のすべてのメソッドのpromisifiedバージョンを作るために使用しています。そして、.then()の代わりに.spread()メソッドを使用して、2つの結果を名前付き引数に自動的に分離します。

使用より拡張機能は、ファイル名任意の配列を処理する

あなたはまた、そのようなアレイを処理し、次いで得られた約束に Promise.all()(上記のコードは、手動で行った何かし Promise.map()、より拡張されたブルーバード機能を使用することができ

)。この場合、あなたはその点で、より一般的な行うことができるファイル名は何でもしたい言語やコードのファイル名の任意のリストで行うことができます:それは(

const Promise = require('bluebird'); 
const fs = Promise.promisifyAll(require('fs')); 

// common helper function 
function readFileSplitWords(file) { 
    return fs.readFileAsync(file, 'utf8').then(function(data) { 
     // make split words be the fulfilled value of the promise 
     return data.split('\n'); 
    }); 
} 

var files = ['frenchVerbsList.text', 'englishVerbsList.txt']; 
Promise.map(files, readFileSplitWords).then(function(results) { 
    // results is an array of arrays where each sub-array is a language list of words 
    // combine the lists (assumes all word lists have the same length) 
    var combinedList = []; 
    var len = results[0].length; 
    // for each word in the first array 
    for (var i = 0; i < len; i++) { 
     // get all the other words in the same array position 
     var words = []; 
     for (var j = 0; j < results.length; j++) { 
      words.push(results[j][i]); 
     } 
     combinedList.push(words.join(',,,')); 
    } 

    // check the result 
    for (i = 0; i < 10; i++) { 
     console.log(combinedList[i]); 
    } 
}, function(err) { 
    // handle an error here 
}); 
+0

そのコードは完璧に見えますが、このエラーが発生します。 combinedList [i] = frenchWords [i] + ",,," + englishWords [i]; ^ はTypeError:プロパティを設定することはできません '0' 未定義 のprocessResultsで(C:\プログラムファイル\ Interlex 2フレンチ\動詞\ fileOp4.jsを\:32:25 ) Cで:\プログラムファイル\ Interlex 2 FSReqWrap.readFileAfterClose [as oncomplete](fs.js:380:3) – Gerard

+0

@Gerard - これは元のコードの別のエラーです。 'combinedList'を' var combinedList = []; 'のように配列に初期化する必要があります。私はそれを含むすべてのバリエーションを編集しました。 – jfriend00

+0

複数の約束ベースのバージョンを追加しました。 – jfriend00

1

を私の心を保つためにこれをやっていますファイルを読む)を完了し、次に続行します。

代わりにreadFileSyncを使用してください(ただし、ほとんどの場合、非同期バージョンを使用することをお勧めします。サーバーの一部ではなくローカルの「ツール」スクリプトでない限り)。

1

コードの結合部分に到達すると、ファイルの読み取りが完了したことを示す何もないので、両方のファイルが同じ速度で処理されることは保証されません。

あなたがする必要があるのは、リストを結合する前に両方のファイルが完了していることを確認することです。あなたはasyncライブラリを使ってそれを行うことができます。あなたが持っている関数の1つはparallelで、2つの関数(あなたの場合はファイルごとに1つ)を除いて並列に実行することができます。そして、両方が実行されると実行される第3のコールバック関数

関連する問題