非同期関数を使用すると、Promise ベースのコードをあたかも同期であるかのように記述できます。
Chrome、Edge、Firefox、Safari では非同期関数がデフォルトで有効になっています。 率直に言って 素晴らしいことですこれを使用すると、Promise ベースのコードを メインスレッドをブロックせずに同期されていた場合。 あまり「巧妙」ではない非同期コード見やすくなるからです
非同期関数は次のように動作します。
async function myFirstAsyncFunction() {
try {
const fulfilledValue = await promise;
} catch (rejectedValue) {
// …
}
}
関数定義の前に async
キーワードを使用すると、
関数内の await
。Promise に対して await
を実行すると、関数は一時停止します。
Promise が解決するまで、ブロッキングせずに実行します。Promise が履行されると、
価値を取り戻すことができますPromise が拒否された場合は、拒否された値がスローされます。
ブラウザ サポート
対応ブラウザ
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
例: 取得をログに記録する
URL を取得し、レスポンスをテキストとして記録するとします。画面サンプル Promise を使用します。
function logFetch(url) {
return fetch(url)
.then((response) => response.text())
.then((text) => {
console.log(text);
})
.catch((err) => {
console.error('fetch failed', err);
});
}
非同期関数を使用した場合も同じです。
async function logFetch(url) {
try {
const response = await fetch(url);
console.log(await response.text());
} catch (err) {
console.log('fetch failed', err);
}
}
行数は同じですが、コールバックはすべてなくなっています。これにより、 特に Promise に詳しくない人にとっては読みやすくなります。
非同期戻り値
非同期関数は、await
を使用するかどうかにかかわらず、常に Promise を返します。
Promise は、非同期関数が返したものを使用して解決するか、
非同期関数がスローするもの次のようになります。
// wait ms milliseconds
function wait(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function hello() {
await wait(500);
return 'world';
}
hello()
を呼び出すと、"world"
で満たされる Promise が返されます。
async function foo() {
await wait(500);
throw Error('bar');
}
foo()
を呼び出すと、Error('bar')
で拒否する Promise が返されます。
例: レスポンスのストリーミング
複雑な例の場合、非同期関数のメリットはさらに大きくなります。たとえば チャンクのログアウト中にレスポンスをストリーミングし、最終的なサイズを返します。
Promise の場合は次のようになります。
function getResponseSize(url) {
return fetch(url).then((response) => {
const reader = response.body.getReader();
let total = 0;
return reader.read().then(function processResult(result) {
if (result.done) return total;
const value = result.value;
total += value.length;
console.log('Received chunk', value);
return reader.read().then(processResult);
});
});
}
見てください、ジェイク「約束の使い手」ですArchibald。通話状況を確認
processResult()
を内部に使用して非同期ループを設定しますか?心に響く作風
とても頭がいいと感じます。しかし、ほとんどの「スマート」コードを見落とさないように、
何が起こっているのかを理解できません
あります
非同期関数でもう一度試してみましょう。
async function getResponseSize(url) {
const response = await fetch(url);
const reader = response.body.getReader();
let result = await reader.read();
let total = 0;
while (!result.done) {
const value = result.value;
total += value.length;
console.log('Received chunk', value);
// get the next result
result = await reader.read();
}
return total;
}
「スマート」なくなります。非同期ループで、私を自分に甘く感じたのは、
代わりに 信頼できる退屈な while ループに置き換わります。はるかに良くなりました。将来的には、
非同期イテレータ
これは、
while
ループを for-of ループに置き換えて、さらにすっきりさせます。
その他の非同期関数の構文
async function() {}
についてはすでに説明しましたが、async
キーワードは次のように設定できます。
次の関数構文で使用できます。
アロー関数
// map some URLs to json-promises
const jsonPromises = urls.map(async (url) => {
const response = await fetch(url);
return response.json();
});
オブジェクトのメソッド
const storage = {
async getAvatar(name) {
const cache = await caches.open('avatars');
return cache.match(`/avatars/${name}.jpg`);
}
};
storage.getAvatar('jaffathecake').then(…);
クラス メソッド
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jaffathecake').then(…);
気をつけて!順番通りにしすぎない
同期しているように見えるコードを記述していますが、 並列して実行する機会が得られます
async function series() {
await wait(500); // Wait 500ms…
await wait(500); // …then wait another 500ms.
return 'done!';
}
上記の処理が完了するまでに 1, 000 ミリ秒かかりますが、次の処理は完了です。
async function parallel() {
const wait1 = wait(500); // Start a 500ms timer asynchronously…
const wait2 = wait(500); // …meaning this timer happens in parallel.
await Promise.all([wait1, wait2]); // Wait for both timers in parallel.
return 'done!';
}
両方の待機が同時に発生するため、上記が完了するまでに 500 ミリ秒かかります。 実用的な例を見てみましょう。
例: フェッチを順番に出力する
一連の URL をフェッチして、これらの URL をできるだけ早く 正しい順序になります。
深呼吸 - Promise では次のようになります。
function markHandled(promise) {
promise.catch(() => {});
return promise;
}
function logInOrder(urls) {
// fetch all the URLs
const textPromises = urls.map((url) => {
return markHandled(fetch(url).then((response) => response.text()));
});
// log them in order
return textPromises.reduce((chain, textPromise) => {
return chain.then(() => textPromise).then((text) => console.log(text));
}, Promise.resolve());
}
そうです。reduce
を使用して一連の Promise を連結しています。たぶん
スマート。ただし、これは非常にスマートなコーディングなので、使わない方が得策です。
ただし、上記を非同期関数に変換すると、 逐次的すぎる:
async function logInOrder(urls) { for (const url of urls) { const response = await fetch(url); console.log(await response.text()); } }
function markHandled(...promises) { Promise.allSettled(promises); } async function logInOrder(urls) { // fetch all the URLs in parallel const textPromises = urls.map(async (url) => { const response = await fetch(url); return response.text(); }); markHandled(...textPromises); // log them in sequence for (const textPromise of textPromises) { console.log(await textPromise); } }
ブラウザ サポートの回避策: ジェネレータ
ジェネレータ( すべての主要なブラウザの最新バージョンに )を使用すると、非同期関数をポリフィルできます。
Babel が代わりに行いますが、 Babel REPL を使用した例をご覧ください
- トランスパイルされたコードの類似性に注意してください。この変換は Babel の es2017 プリセット
トランスパイル方法をおすすめします。 対象のブラウザは非同期関数をサポートしていますが、 Transpiler を使用すると、 Babel のポリフィル ご自身で使用してみてください。従来の方法:
async function slowEcho(val) {
await wait(1000);
return val;
}
ポリフィルを含める 次のように記述します。
const slowEcho = createAsyncFunction(function* (val) {
yield wait(1000);
return val;
});
なお、ジェネレータ(function*
)を createAsyncFunction
に渡す必要があることに留意してください。
await
の代わりに yield
を使用します。それ以外は同じように動作します。
回避策: リジェネレータ
古いブラウザをターゲットにしている場合、Babel では ジェネレータのトランスパイルも可能です IE8 まで非同期関数を使用できます。これを行うには、 Babel の es2017 プリセット es2015 プリセットを指定します。
出力があまり見栄えが良くないため、 減ります。
すべてを非同期にします。
すべてのブラウザで非同期関数を導入したら、すべてのブラウザで Promise を返す関数です。コードを整理できるだけでなく 関数が常に Promise を返すことを確認します。
先日、非同期関数にとても興味を持ちました。 2014 年、 実際にブラウザに表示されるのは素晴らしいことです。やりました!