今回はjsでのasync関数の使い方と、jsでasync関数を使う際の注意点を紹介します。以下は実際的なケースですので見てみましょう。
1. 究極の解決策
JavaScript プログラミングにおいて非同期操作は厄介な問題であり、人々はこの問題を解決するためにさまざまな解決策を提案しています。
最初のコールバック関数から、Promiseオブジェクト、そしてGenerator関数に至るまで、毎回改良が加えられてきましたが、不完全であるように感じられます。これらはすべてさらに複雑であり、抽象化の基礎となる動作メカニズムを理解する必要があります。
非同期 I/O は単にファイルを読み取るだけではないでしょうか? なぜそれほど複雑にする必要があるのでしょうか?非同期プログラミングの最高の状態は、非同期かどうかをまったく心配する必要がないことです。
async 関数はトンネルの終わりの光であり、多くの人がこれを非同期操作の究極のソリューションと考えています。
2. 非同期関数とは何ですか?
一言で言えば、async 関数は Generator 関数の糖衣構文です。
前の記事には、2つのファイルを順番に読み取るジェネレーター関数があります。
var fs = require('fs'); var readFile = function (fileName){ return new Promise(function (resolve, reject){ fs.readFile(fileName, function(error, data){ if (error) reject(error); resolve(data); }); }); }; var gen = function* (){ var f1 = yield readFile('/etc/fstab'); var f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };
以下のように非同期関数として記述します。
var asyncReadFile = async function (){ var f1 = await readFile('/etc/fstab'); var f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };
比較すると、async 関数は Generator 関数のアスタリスク (*) を async に置き換え、yield を await に置き換えるだけであることがわかります。
Generator 関数に対する async 関数の改善点は以下の 3 点に反映されています。
(1) アクチュエーター内蔵。 Generator 関数の実行はエグゼキュータに依存する必要があるため、co 関数ライブラリがあり、async 関数には独自のエグゼキュータが付属しています。つまり、async 関数の実行は通常の関数とまったく同じで、行は 1 行だけです。
var result = asyncReadFile();
(2) セマンティクスの改善。 async と await は、アスタリスクや yield よりも明確なセマンティクスを持っています。 async は関数内に非同期操作があることを意味し、await は次の 式 が結果を待つ必要があることを意味します。
(3) 適用範囲が広がります。 co 関数ライブラリの規約によれば、yield コマンドの後には Thunk 関数または Promise オブジェクトのみを続けることができますが、async 関数の await コマンドの後には Promise オブジェクトとプリミティブ型の値 (数値、) を続けることができます。文字列、およびブール値、ただしこれは同期操作と同等です)。
async関数の実装は、Generator関数と自動実行関数を関数内にラップすることです。
async function fn(args){ // ... } // 等同于 function fn(args){ return spawn(function*() { // ... }); }
すべての非同期関数は、上記の 2 番目の形式で記述することができます。ここでは、spawn 関数が自動実行関数です。
spawn 関数の実装を以下に示します。これは基本的に以前の自動実行プログラムのレプリカです。
function spawn(genF) { return new Promise(function(resolve, reject) { var gen = genF(); function step(nextF) { try { var next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); }
async 関数は、ES6 ではなく ES7 に属する非常に新しい構文機能です。現在はまだ提案段階ですが、トランスコーダのBabelやregeneratorではすでに対応しており、トランスコード後に利用することが可能です。
Generator 関数と同様に、async 関数は Promise オブジェクトを返し、then メソッドを使用してコールバック関数を追加できます。関数の実行時に await が発生すると、最初に戻り、トリガーされた非同期操作が完了するまで待機してから、関数本体内の後続のステートメントを実行します。
これが例です。
async function getStockPriceByName(name) { var symbol = await getStockSymbol(name); var stockPrice = await getStockPrice(symbol); return stockPrice; } getStockPriceByName('goog').then(function (result){ console.log(result); });
上記のコードは株価を取得する関数です。関数の前にある async キーワードは、関数内に非同期操作があることを示しています。この関数が呼び出されると、Promise オブジェクトがすぐに返されます。
次の例では、値を出力するミリ秒数を指定します。
function timeout(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function asyncPrint(value, ms) { await timeout(ms); console.log(value) } asyncPrint('hello world', 50);
上記のコードは、50 ミリ秒後に「hello world」が出力されることを指定しています。
await コマンドの背後にある Promise オブジェクトは拒否される可能性があるため、await コマンドを try...catch コード ブロックに置くのが最善です。
async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另一种写法 async function myFunction() { await somethingThatReturnsAPromise().catch(function (err){ console.log(err); }); }
await コマンドは非同期関数でのみ使用できます。通常の関数で使用するとエラーが報告されます。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 报错 docs.forEach(function (doc) { await db.post(doc); }); }
上面代码会报错,因为 await 用在普通函数之中了。但是,如果将 forEach 方法的参数改成 async 函数,也有问题。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 可能得到错误结果 docs.forEach(async function (doc) { await db.post(doc); }); }
上面代码可能不会正常工作,原因是这时三个 db.post 操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用 for 循环。
async function dbFuc(db) { let docs = [{}, {}, {}]; for (let doc of docs) { await db.post(doc); } }
如果确实希望多个请求并发执行,可以使用 Promise.all 方法。
async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = await Promise.all(promises); console.log(results); } // 或者使用下面的写法 async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = []; for (let promise of promises) { results.push(await promise); } console.log(results); }
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
以上がjsでasync関数を使う方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。