web-dev-qa-db-ja.com

Javascriptでasync / awaitを並行して実行する方法

最終的にasync/awaitは、IEを除くすべての主要ブラウザで サポート になります。これで、async/awaitを使用して、より読みやすいコードの作成を開始できますが、キャッチがあります。多くの人がこのように非同期待機を使用しています:

const userResponse = await fetchUserAsync();
const postsResponse = await fetchPostsAsync();

このコードは読み取り可能ですが、問題がありますが、関数を連続して実行し、ユーザーの取得が完了するまで投稿の取得を開始しません。解決策は簡単です。リソースを並行して取得する必要があります。

だから私がしたいことは(擬似言語で)です:

fn task() {
  result-1 = doAsync();
  result-2 = doAsync();
  result-n = doLongAsync();

  // handle results together
  combinedResult = handleResults(result-1, result-2);

  lastResult = handleLastResult(result-n);
}
55
NoNameProvided

このようなものを書くことができます:

const responses = await Promise.all([
 fetchUserAsync(),
 fetchPostsAsync(),
]);

const userResponse = responses[0];
const postsResponse = responses[1];

これは簡単ですよね?しかし、キャッチがあります。 Promise.allにはfail-fastの動作があります。つまり、約束の1つが拒​​否されるとすぐに拒否されます。おそらく、フェッチのいずれかの拒否の処理を担当する、より堅牢なソリューションが必要でしょう。幸いなことに解決策があり、Promise.allを使用せずにasync/awaitで簡単に達成できます。実例:

console.clear();

function wait(ms, data) {
  return new Promise( resolve => setTimeout(resolve.bind(this, data), ms) );
}

/** 
 * This will run in series, because 
 * we call a function and immediately wait for it's result, 
 * so this will finish in 1s.
 */
async function series() {
  return {
    result1: await wait(500, 'seriesTask1'),
    result2: await wait(500, 'seriesTask2'),
  }
}

/** 
 * While here we call the functions first,
 * then wait for the result later, so 
 * this will finish in 500ms.
 */
async function parallel() {
  const task1 = wait(500, 'parallelTask1');
  const task2 = wait(500, 'parallelTask2');

  return {
    result1: await task1,
    result2: await task2,
  }
}

async function taskRunner(fn, label) {
  const startTime = performance.now();
  console.log(`Task ${label} starting...`);
  let result = await fn();
  console.log(`Task ${label} finished in ${ Number.parseInt(performance.now() - startTime) } miliseconds with,`, result);
}

void taskRunner(series, 'series');
void taskRunner(parallel, 'parallel');


/* 
 * The result will be:
 * Task series starting...
 * Task parallel starting...
 * Task parallel finished in 500 milliseconds with, { "result1": "parallelTask1", "result2": "parallelTask2" }
 * Task series finished in 1001 milliseconds with, { "result1": "seriesTask1", "result2": "seriesTask2" }
 */

注:このスニペット(またはnodejs v7以降)を実行するには、async/awaitenabled を持つブラウザーが必要です。

このように、単にtry/catchを使用してエラーを処理し、parallel関数内で部分的な結果を返すことができます。

104
NoNameProvided

Promise.allのフェイルファースト動作と、構造化代入構文に問題がない場合:

const [userResponse, postsResponse] = await Promise.all([
  fetchUserAsync(),
  fetchPostsAsync(),
]);
18
ricka

これを実行時に決定された呼び出し数にどのように拡張するかを尋ねる人のために、2つのループを使用できます。最初はすべてのタスクを開始し、2番目はすべてが完了するまで待機します

console.clear();

function wait(ms, data) {
  return new Promise( resolve => setTimeout(resolve.bind(this, data), ms) );
}

/** 
 * While here we call the functions first,
 * then wait for the result later, so 
 * this will finish in 500ms.
 */
async function runTasks(timings) {
  let tasks = [];
  for (let i in timings) {
      tasks.Push(wait(timings[i], `Result of task ${i}`));
  }

  /* Want fast fail? use Promise.All */
  //return Promise.All(tasks);
  
  let results = [];
  for (let task of tasks) {
       results.Push(await task);
  }

  return results;
}

async function taskRunner(fn, arg, label) {
  const startTime = performance.now();
  console.log(`Task ${label} starting...`);
  let result = await fn(arg);
  console.log(`Task ${label} finished in ${ Number.parseInt(performance.now() - startTime) } miliseconds with,`, result);
}

void taskRunner(runTasks, [50,100,200,60,500], 'Task List');
3
Wilco

擬似コードは次のように記述できます。

fn async task() {
  result-1 = doAsync();
  result-2 = doAsync();
  result-n = doLongAsync();
  try{
  // handle results together
  combinedResult = handleResults(await result-1, await result-2);
  lastResult = handleLastResult(await result-n);
  }
  catch(err){
   console.error(err)
  }

}

result-1、result-2、result-nは並行して実行されます。 combinedResultとlastResultも並行して実行されます。ただし、result-1とresult-2が使用可能になると、combinedResult値、つまりhandleResults関数の戻り値が返され、result-nが使用可能になると、lastResult値、つまりhandleLastResultが返されます。

お役に立てれば

0
Nagaraja Malla

私は実際にこれと同じことをしました。 promiseを使用してから、最後にPromise.allを使用して同期することにより、多くの同時リクエストを行うことができますが、終了する前にすべての結果が返されることを確認してください。

最後の例をご覧ください: http://javascriptrambling.blogspot.com/2017/04/to-promised-land-with-asyncawait-and.html

0
Kevin Williams

最初に、あなたのコードはブロッキングコードですか?

はいの場合、javascriptはシングルスレッドであるため、2つの同期コード、たとえば2つのループ(forまたはwhile)を同時に実行することはできません。

しかし、Web Workersを使用することを達成するが可能です。私は、一般的なWeb Workersで関数を実行し、分離されたjsファイルを使用しませんでした。

setInterval(()=>{console.log("non blocked " + Math.random())}, 900)

console.log("start blocking code in parallel in web Worker")
console.time("blocked")

genericWorker(window, ["blockCpu", function (block){    
    block(10000) //This blockCpu function is defined below
    return "\n\nbla bla\n" //This is catched in the resolved promise

}]).then(function (result){
    console.timeEnd("blocked")
    console.log("End of blocking code", result)
})
.catch(function(error) { console.log(error) })


/*  A Web Worker that does not use a File, it create that from a Blob
    @cb_context, The context where the callback functions arguments are, ex: window
    @cb, ["fn_name1", "fn_name2", function (fn1, fn2) {}]
        The callback will be executed, and you can pass other functions to that cb
*/
function genericWorker(cb_context, cb) {
    return new Promise(function (resolve, reject) {

        if (!cb || !Array.isArray(cb))
            return reject("Invalid data")

        var callback = cb.pop()
        var functions = cb

        if (typeof callback != "function" || functions.some((fn)=>{return typeof cb_context[fn] != "function"}))
            return reject(`The callback or some of the parameters: (${functions.toString()}) are not functions`)

        if (functions.length>0 && !cb_context)
            return reject("context is undefined")

        callback = fn_string(callback) //Callback to be executed
        functions = functions.map((fn_name)=> { return fn_string( cb_context[fn_name] ) })

        var worker_file = window.URL.createObjectURL( new Blob(["self.addEventListener('message', function(e) { var bb = {}; var args = []; for (fn of e.data.functions) { bb[fn.name] = new Function(fn.args, fn.body); args.Push(fn.name)}; var callback = new Function( e.data.callback.args, e.data.callback.body); args = args.map(function(fn_name) { return bb[fn_name] });  var result = callback.apply(null, args) ;self.postMessage( result );}, false)"]) )
        var worker = new Worker(worker_file)

        worker.postMessage({ callback: callback, functions: functions })

        worker.addEventListener('error', function(error){ return reject(error.message) })

        worker.addEventListener('message', function(e) {
            resolve(e.data), worker.terminate()
        }, false)

        //From function to string, with its name, arguments and its body
        function fn_string (fn) {
            var name = fn.name, fn = fn.toString()

            return { name: name, 
                args: fn.substring(fn.indexOf("(") + 1, fn.indexOf(")")),
                body: fn.substring(fn.indexOf("{") + 1, fn.lastIndexOf("}"))
            }
        }
    })
}

//random blocking function
function blockCpu(ms) {
    var now = new Date().getTime(), result = 0
    while(true) {
        result += Math.random() * Math.random();
        if (new Date().getTime() > now +ms)
            return;
    }   
}
0