11

例として、どこかからファイルのリストを取得し、これらのファイルの内容をロードして、最終的にユーザーに表示したいとします。同期モデルでは、次のようになります (疑似コード)。

var file_list = fetchFiles(source);

if (!file_list) {
    display('failed to fetch list');

} else {
        for (file in file_list) { // iteration, not enumeration
        var data = loadFile(file);

        if (!data) {
            display('failed to load: ' + file);
        } else {
            display(data);
        }
    }
}

これにより、適切なフィードバックがユーザーに提供され、必要に応じてコードの一部を関数に移動できます。人生は単純。

さて、私の夢を打ち砕くために:fetchFiles()loadFile()は実際には非同期です。簡単な方法は、それらを同期関数に変換することです。しかし、呼び出しが完了するのを待ってブラウザがロックアップする場合、これは良くありません。

古典的な縮小広告スパゲタムのように、コールバックの無限のチェーンを深く掘り下げずに、複数の相互依存および/または階層化された非同期呼び出しを処理するにはどうすればよいですか? コードを疎結合に保ちながら、これらをきれいに処理する実証済みのパラダイムはありますか?

4

6 に答える 6

6

Deferred は本当にここに行く方法です。これらは、あなた (および多くの非同期コード) が必要とするものを正確にキャプチャします。

また、それらを使用するために jQuery は必要ありません。進取の気性に富んだ個人がDeferred を underscoreに移植し、それを使用するのに underscore さえ必要ないと主張しています。

したがって、コードは次のようになります。

function fetchFiles(source) {
    var dfd = _.Deferred();

    // do some kind of thing that takes a long time
    doExpensiveThingOne({
        source: source,
        complete: function(files) {
            // this informs the Deferred that it succeeded, and passes
            // `files` to all its success ("done") handlers
            dfd.resolve(files);

            // if you know how to capture an error condition, you can also
            // indicate that with dfd.reject(...)
        }
    });

    return dfd;
}

function loadFile(file) {
    // same thing!
    var dfd = _.Deferred();

    doExpensiveThingTwo({
        file: file,
        complete: function(data) {
            dfd.resolve(data);
        }
    });

    return dfd;
}

// and now glue it together
_.when(fetchFiles(source))
.done(function(files) {
    for (var file in files) {
        _.when(loadFile(file))
        .done(function(data) {
            display(data);
        })
        .fail(function() {
            display('failed to load: ' + file);
        });
    }
})
.fail(function() {
    display('failed to fetch list');
});

セットアップは少し冗長ですが、Deferred の状態を処理するコードを書き、それを再び気にする必要のない関数に詰め込むと、イベントの実際の流れをいじることができます。簡単に。例えば:

var file_dfds = [];
for (var file in files) {
    file_dfds.push(loadFile(file));
}

_.when(file_dfds)
.done(function(datas) {
    // this will only run if and when ALL the files have successfully
    // loaded!
});
于 2013-01-23T21:21:12.683 に答える
3

イベント

たぶん、イベントを使用することは良い考えです。これにより、コードツリーを作成できなくなり、コードが分離されます。

イベントのフレームワークとしてBeanを使用しました。

擬似コードの例

// async request for files
function fetchFiles(source) {

    IO.get(..., function (data, status) {
        if(data) {
            bean.fire(window, 'fetched_files', data);
        } else {
            bean.fire(window, 'fetched_files_fail', data, status);
        } 
    });

}

// handler for when we get data
function onFetchedFiles (event, files) {
    for (file in files) { 
        var data = loadFile(file);

        if (!data) {
            display('failed to load: ' + file);
        } else {
            display(data);
        }
    }
}

// handler for failures
function onFetchedFilesFail (event, status) {
    display('Failed to fetch list. Reason: ' + status);
}

// subscribe the window to these events
bean.on(window, 'fetched_files', onFetchedFiles);
bean.on(window, 'fetched_files_fail', onFetchedFilesFail);

fetchFiles();

カスタムイベントとこの種のイベント処理は、事実上すべての一般的なJSフレームワークに実装されています。

于 2013-01-29T19:04:04.807 に答える
2

jQueryを使用したくない場合は、代わりにWebワーカーを同期リクエストと組み合わせて使用​​できます。Webワーカーは、10より前のバージョンのInternet Explorerを除いて、すべての主要なブラウザーでサポートされています。

WebWorkerブラウザーの互換性

基本的に、Webワーカーが何であるかが完全にわからない場合は、ブラウザーがメインスレッドに影響を与えることなく別のスレッドで特殊なJavaScriptを実行する方法と考えてください(警告:シングルコアCPUでは、両方のスレッドが実行されます幸いなことに、最近のほとんどのコンピューターにはデュアルコアCPUが搭載されています。)。通常、Webワーカーは、複雑な計算や集中的な処理タスクのために予約されています。Webワーカー内のコードはDOMを参照できず、渡されていないグローバルデータ構造も参照できないことに注意してください。基本的に、Webワーカーはメインスレッドから独立して実行されます。ワーカーが実行するコードは、JavaScriptコードベースの残りの部分とは別に、独自のJSファイル内に保持する必要があります。さらに、Webワーカーが適切に機能するために特定のデータが必要な場合は、起動時にそのデータをWebワーカーに渡す必要があります。

注目に値するさらにもう1つの重要な点は、ファイルのロードに使用する必要のあるJSライブラリは、ワーカーが実行するJavaScriptファイルに直接コピーする必要があるということです。つまり、これらのライブラリは最初に縮小され(まだ縮小されていない場合)、次にコピーしてファイルの先頭に貼り付ける必要があります。

とにかく、私はこれにアプローチする方法を示すために基本的なテンプレートを書くことにしました。以下でチェックしてください。質問/批評などをお気軽にどうぞ。

メインスレッドで実行し続けたいJSファイルで、ワーカーを呼び出すために次のコードのようなものが必要です。

function startWorker(dataObj)
{
    var message = {},
        worker;

      try
      {
        worker = new Worker('workers/getFileData.js');
      } 
      catch(error) 
      {
        // Throw error
      }

    message.data = dataObj;

    // all data is communicated to the worker in JSON format
    message = JSON.stringify(message);

    // This is the function that will handle all data returned by the worker
    worker.onMessage = function(e)
    {
        display(JSON.parse(e.data));
    }

    worker.postMessage(message);
}

次に、ワーカー用の別のファイルに(上記のコードでわかるように、ファイルに名前を付けましたgetFileData.js)、次のように記述します...

function fetchFiles(source)
{
    // Put your code here
    // Keep in mind that any requests made should be synchronous as this should not
    // impact the main thread
}

function loadFile(file)
{
    // Put your code here
    // Keep in mind that any requests made should be synchronous as this should not
    // impact the main thread
}

onmessage = function(e)
{
    var response = [],
        data = JSON.parse(e.data),
        file_list = fetchFiles(data.source),
        file, fileData;

    if (!file_list) 
    {
        response.push('failed to fetch list');
    }
    else 
    {
        for (file in file_list) 
        { // iteration, not enumeration
            fileData = loadFile(file);

            if (!fileData) 
            {
                response.push('failed to load: ' + file);
            } 
            else 
            {
                response.push(fileData);
            }
        }
    }

    response = JSON.stringify(response);

    postMessage(response);

    close();
}

PS:また、同期リクエストをWebワーカーと組み合わせて使用​​することの長所と短所を理解するのに役立つ別のスレッドを掘り下げました。

スタックオーバーフロー-Webワーカーと同期リクエスト

于 2013-01-23T17:27:36.943 に答える
2

jQuery Deferredが必要なようです。以下は、正しい方向を示すのに役立つ可能性のあるテストされていないコードです。

$.when(fetchFiles(source)).then(function(file_list) { 
  if (!file_list) {
    display('failed to fetch list');
  } else {
    for (file in file_list) {
      $.when(loadFile(file)).then(function(data){
        if (!data) {
          display('failed to load: ' + file);
        } else {
          display(data);
        }
      });
    }
  }
});

また、Deferred オブジェクトのいくつかの使用例を示す別の適切な投稿も見つけました。

于 2013-01-18T21:52:52.730 に答える
1

作業中のWebアプリでこの問題が発生しました。これが、(ライブラリなしで)解決した方法です。

ステップ1非常に軽量なpubsub実装を作成しました。特別なことは何もありません。購読、購読解除、公開、ログ記録。すべて(コメント付き)で93行のJavascriptが追加されます。gzipの前に2.7kb。

ステップ2:pubsub実装に手間のかかる作業を行わせることで、達成しようとしていたプロセスを切り離しました。次に例を示します。

// listen for when files have been fetched and set up what to do when it comes in
pubsub.notification.subscribe(
    "processFetchedResults", // notification to subscribe to
    "fetchedFilesProcesser", // subscriber

    /* what to do when files have been fetched */ 
    function(params) {

        var file_list = params.notificationParams.file_list;

        for (file in file_list) { // iteration, not enumeration
        var data = loadFile(file);

        if (!data) {
            display('failed to load: ' + file);
        } else {
            display(data);
        }
    }
);    

// trigger fetch files 
function fetchFiles(source) {

   // ajax call to source
   // on response code 200 publish "processFetchedResults"
   // set publish parameters as ajax call response
   pubsub.notification.publish("processFetchedResults", ajaxResponse, "fetchFilesFunction");
}

もちろん、これはセットアップでは非常に冗長であり、舞台裏の魔法はほとんどありません。技術的な詳細は次のとおりです。

  1. setTimeoutサブスクリプションのトリガーを処理するために使用しています。このように、それらはノンブロッキング方式で実行されます。

  2. 呼び出しは、処理から効果的に切り離されます。通知に別のサブスクリプションを記述し"processFetchedResults"、応答が届いたら複数のこと(たとえば、ロギングと処理)を実行しながら、それらを非常に分離された、小さくて管理しやすいコードブロックに保持できます。

  3. 上記のコードサンプルは、フォールバックに対応しておらず、適切なチェックを実行していません。生産基準に到達するには、少しのツールが必要になると確信しています。それがどれほど可能であり、ソリューションがライブラリに依存しないかを示したかっただけです。

乾杯!

于 2013-01-29T16:30:09.870 に答える
1

asyncは、node.js でよく使用される一般的な非同期フロー制御ライブラリです。私はブラウザで個人的に使用したことはありませんが、ブラウザでも動作するようです。

この例では、(理論的には) 2 つの関数を実行し、すべてのファイル名とその読み込みステータスのオブジェクトを返します。 async.mapは並行して実行されますwaterfallが、 はシリーズであり、各ステップの結果を次のステップに渡します。

ここでは、2 つの非同期関数がコールバックを受け入れると想定しています。そうでない場合は、それらがどのように使用されることを意図しているかについて、より多くの情報が必要です (完了時にイベントを発生させますか? など)。

async.waterfall([
  function (done) {
    fetchFiles(source, function(list) {
      if (!list) done('failed to fetch file list');
      else done(null, list);
    });
    // alternatively you could simply fetchFiles(source, done) here, and handle
    // the null result in the next function.
  },

  function (file_list, done) {
    var loadHandler = function (memo, file, cb) {
      loadFile(file, function(data) {
        if (!data) {
          display('failed to load: ' + file);
        } else {
          display(data);
        }
        // if any of the callbacks to `map` returned an error, it would halt 
        // execution and pass that error to the final callback.  So we don't pass
        // an error here, but rather a tuple of the file and load result.
        cb(null, [file, !!data]);
      });
    };
    async.map(file_list, loadHandler, done);
  }
], function(err, result) {
  if (err) return display(err);
  // All files loaded! (or failed to load)
  // result would be an array of tuples like [[file, bool file loaded?], ...]
});

waterfall関数の配列を受け入れて順番に実行し、それぞれの結果を引数として次の引数として渡し、最後の引数としてコールバック関数を渡します。これは、エラーまたは関数からの結果データで呼び出します。

もちろん、コードの構造をまったく変更することなく、これら 2 つの間または前後に任意の数の異なる非同期コールバックを追加できます。 waterfallは実際には 10 の異なるフロー制御構造のうちの 1 つにすぎないため、多くのオプションがあります (ただし、ほとんどの場合、最終的に を使用autoすることになりますが、これにより、Makefile のような要件構文を介して同じ関数で並列実行と直列実行を混在させることができます)。

于 2013-01-23T18:08:57.890 に答える