BFE.devでのAI解答例 - JavaScript コーディング問題
31. async helper - `race()`を実装する
解法:
race()
関数は以下のように実装できます。この実装では、必要な Promise インスタンスを Promise.all() で利用し、 Promise.race() を使うことで、最も速く実行された結果またはエラーを返します。
function race(funcArray) { return (callback, data) => { Promise.all( funcArray.map((func) => { return new Promise((resolve, reject) => { func((error, result) => { if (error) { reject(error); } else { resolve(result); } }, data); }); }) ) .then((results) => { callback(null, results[0]); }) .catch((error) => { callback(error, null); }); };}
エッセンシャル関数についての説明
race(funcArray)
引数に AsyncFunc
配列 funcArray
を受け取り、新しい関数を返します。この関数は一つでもfunction実行終わる時、またはエラー発生時に実行されます。内部的には Promise
を生成しています。
/** * Generate new function to execute functions in parallel * * @param {AsyncFunc[]} funcArray - functions to execute * @return {Function} function to execute parallel execution */function race(funcArray) { return (callback, data) => { ... };}
Promise.all()
2021 年 9 月時点で、 Promise.all()
は全ての渡された Promise
の解決状態を待機するために最もよく使われる方法です。渡された Promise
が解決されると、function はそれぞれの解決結果を含む配列を返します。
const promiseArray = [ new Promise((resolve) => setTimeout(() => resolve("promise1"), 0)), new Promise((resolve) => setTimeout(() => resolve("promise2"), 500)), new Promise((resolve) => setTimeout(() => resolve("promise3"), 1000))];Promise.all(promises) // 全てが解決された時点で以下を出力する .then((results) => console.log(results));// 結果:
Promise.race()
Promise.race()
は、配列に渡された Promise
のうち、最初に解決された Promise
だけを返します。
const promiseArray = [ new Promise((resolve) => setTimeout(() => resolve("promise1"), 0)), new Promise((resolve) => setTimeout(() => resolve("promise2"), 500)), new Promise((resolve) => setTimeout(() => resolve("promise3"), 1000))];Promise.race(promises) // 最初に解決されるPromise を出力する .then((results) => console.log(result));// 結果:// 'promise1'
callback( )
callback() 関数は、コールバック関数の呼び出し元で、エラーと解決結果を含んだ関数です。
/** * Sample AsyncFunc to add two numbers * * @param {Callback} callback - the callback that returns the result and any errors * @param {number} data - an array of two numbers to be added */function asyncAdd(callback, data) { const result = data[0] + data[1]; callback(null, result);}
配列の map() 関数
map() 関数は新しい配列を作成します。オリジナルの配列から、各要素について関数を呼び出し、各呼び出しの結果を含む新しい配列を返します。 map() は、配列の元の順序を維持したまま、現在のインデックス、処理される要素の値、元の配列に対する参照を利用します。この場合、 funcArray
配列を map() 関数によって処理します。
/** * Process each function with it's data in parallel */Promise.all( funcArray.map((func) => { return new Promise((resolve, reject) => { func((error, result) => { if (error) { reject(error); } else { resolve(result); } }, data); }); }));
promise.then( )
then() 関数は、 Promise
が解決されたときに呼び出される処理を返します。この then() 関数は、race() 関数の戻り値である関数が呼び出された場合に使用されます。 Promise.all() で全ての Promise が解決されたら、then() 関数が実行されます。最初の解決で、 race() 関数の errorCallback
が false であるとともに、最初に解決された関数の結果を含む配列が返されます。
/** * Process each function with it's data in parallel */Promise.all( funcArray.map((func) => { return new Promise((resolve, reject) => { func((error, result) => { if (error) { reject(error); } else { resolve(result); } }, data); }); })) .then((results) => { callback(null, results[0]); })
ここでの callback
関数は、 race()
関数が実行されたときに最初に解決された関数の結果が渡されます。そのため、 results[0]
をコールバック関数に渡すことができます。全ての関数がエラーを返した場合、this の場合はcatch
またはcallback
でエラーを処理します。
.catch((error) => { callback(error, null); });
解決されていない問題
この実装では、最初にエラーを返した関数のエラーメッセージはロギングされません。他にもたくさんありますが、上記の解決方法により問題が解決されるはずです。
完全なコード:
/** * Generate new function to execute functions in parallel * * @param {AsyncFunc[]} funcArray - functions to execute * @return {Function} function to execute parallel execution */function race(funcArray) { return (callback, data) => { Promise.all( funcArray.map((func) => { return new Promise((resolve, reject) => { func((error, result) => { if (error) { reject(error); } else { resolve(result); } }, data); }); }) ) .then((results) => { callback(null, results[0]); }) .catch((error) => { callback(error, null); }); };}// Sample async functions to test async helper functionsconst async1 = (callback) => { setTimeout(() => callback(undefined, 1), 300);};const async2 = (callback) => { setTimeout(() => callback(undefined, 2), 100);};const async3 = (callback) => { setTimeout(() => callback(undefined, 3), 200);};/* sample usage */const first = race([async1, async2, async3]);first((error, data) => { console.log(data); // 2}, 1);