await
:Promiseの使用方法await
と解決済みのPromiseawait
と拒否されたPromiseawait
は浅い(コールバック内では使用できない)await
の使用 [ES2022]await
await
:非同期関数を順次実行するawait
:非同期関数を並行して実行するawait
は不要await
して結果を無視しても意味がある場合がある概して、非同期関数はPromiseを使用するコードにとってより良い構文を提供します。非同期関数を使用するには、そのためPromiseを理解する必要があります。前の章で説明されています。
次の非同期関数について考えてみましょう。
async function fetchJsonAsync(url) {
try {
const request = await fetch(url); // async
const text = await request.text(); // async
return JSON.parse(text); // sync
}catch (error) {
.fail(error);
assert
} }
前の、かなり同期的に見えるコードは、Promiseを直接使用する次のコードと同等です。
function fetchJsonViaPromises(url) {
return fetch(url) // async
.then(request => request.text()) // async
.then(text => JSON.parse(text)) // sync
.catch(error => {
.fail(error);
assert;
}) }
非同期関数fetchJsonAsync()
に関するいくつかの観察事項
非同期関数はキーワードasync
でマークされます。
非同期関数の本体内では、Promiseベースのコードを同期的なものとして記述します。await
演算子を適用する必要があるのは、値がPromiseである場合だけです。その演算子は非同期関数を一時停止し、Promiseが解決されると再開します。
await
は解決値を返します。await
は拒否値をスローします。非同期関数の結果は常にPromiseです。
fetchJsonAsync()
とfetchJsonViaPromises()
の両方は、このようにまったく同じ方法で呼び出されます。
fetchJsonAsync('http://example.com/person.json')
.then(obj => {
.deepEqual(obj, {
assertfirst: 'Jane',
last: 'Doe',
;
}); })
非同期関数は、Promiseを直接使用する関数と同様にPromiseベースです
外部から見ると、非同期関数とPromiseを返す関数との違いを判別することは事実上不可能です。
JavaScriptには、同期の呼び出し可能なエンティティの次の非同期バージョンがあります。それらの役割は常に実際の関数またはメソッドのいずれかです。
// Async function declaration
async function func1() {}
// Async function expression
const func2 = async function () {};
// Async arrow function
const func3 = async () => {};
// Async method definition in an object literal
const obj = { async m() {} };
// Async method definition in a class definition
class MyClass { async m() {} }
非同期関数 vs. 非同期関数
用語「非同期関数」と「async関数」の違いは微妙ですが重要です。
非同期関数とは、結果を非同期的に配信する関数のことです。たとえば、コールバックベースの関数やPromiseベースの関数など。
async関数は、async
とawait
キーワードを含む特別な構文で定義されます。これらの2つのキーワードのためにasync/awaitとも呼ばれます。async関数はPromiseに基づいており、したがって非同期関数でもあります(やや混乱を招きます)。
各非同期関数は常にPromiseを返します。
非同期関数内では、return
(A行)を介して結果のPromiseを解決します。
async function asyncFunc() {
return 123; // (A)
}
asyncFunc()
.then(result => {
.equal(result, 123);
assert; })
通常どおり、何も明示的に返さない場合、undefined
が返されます。
async function asyncFunc() {
}
asyncFunc()
.then(result => {
.equal(result, undefined);
assert; })
throw
(A行)を介して結果のPromiseを拒否します。
async function asyncFunc() {
throw new Error('Problem!'); // (A)
}
asyncFunc()
.catch(err => {
.deepEqual(err, new Error('Problem!'));
assert; })
非同期関数からPromise p
を返す場合、p
は関数の結果になります(あるいはむしろ、結果はp
に「固定」され、まったく同じように動作します)。つまり、Promiseは別のPromiseにラップされません。
async function asyncFunc() {
return Promise.resolve('abc');
}
asyncFunc()
.then(result => assert.equal(result, 'abc'));
次の状況では、すべてのPromise q
が同様に扱われることを思い出してください。
new Promise((resolve, reject) => { ··· })
内のresolve(q)
.then(result => { ··· })
内のreturn q
.catch(err => { ··· })
内のreturn q
非同期関数は次のように実行されます。
p
は、非同期関数が開始されたときに作成されます。p
を解決しながら、実行を永久的に終了できます。return
はp
を解決します。throw
はp
を拒否します。await
を介して別のPromise q
の解決を待っている場合、実行は一時的に終了することもできます。非同期関数は一時停止し、実行は関数を終了します。q
が解決されると再開されます。p
が返されます。結果p
の解決の通知は、Promiseの場合常にそうであるように、非同期的に発生することに注意してください。
次のコードは、非同期関数が同期的に開始され(A行)、次に現在のタスクが終了し(C行)、次に結果のPromiseが非同期的に解決される(B行)ことを示しています。
async function asyncFunc() {
console.log('asyncFunc() starts'); // (A)
return 'abc';
}asyncFunc().
then(x => { // (B)
console.log(`Resolved: ${x}`);
;
})console.log('Task ends'); // (C)
// Output:
// 'asyncFunc() starts'
// 'Task ends'
// 'Resolved: abc'
await
:Promiseの使用方法await
演算子は、非同期関数と非同期ジェネレーター(42.2章「非同期ジェネレーター」で説明)内でのみ使用できます。そのオペランドは通常Promiseであり、次の手順が実行されます。
yield
の動作に似ています。await
は解決値を返します。await
は拒否値をスローします。await
がさまざまな状態のPromiseをどのように処理するかについて、さらに読み進めてください。
await
と解決済みのPromiseオペランドが解決済みのPromiseである場合、await
はその解決値を返します。
.equal(await Promise.resolve('yes!'), 'yes!'); assert
Promise以外の値も許可されており、単に渡されます(非同期関数を一時停止せずに同期的に)。
.equal(await 'yes!', 'yes!'); assert
await
と拒否されたPromiseオペランドが拒否されたPromiseである場合、await
は拒否値をスローします。
try {
await Promise.reject(new Error());
.fail(); // we never get here
assertcatch (e) {
} .equal(e instanceof Error, true);
assert }
演習:非同期関数によるFetch API
exercises/async-functions/fetch_json2_test.mjs
await
は浅い(コールバック内では使用できない)非同期関数内にいて、await
を使用して一時停止したい場合、その関数内で直接実行する必要があります。ネストされた関数(コールバックなど)内では使用できません。つまり、一時停止は浅いです。
たとえば、次のコードは実行できません。
async function downloadContent(urls) {
return urls.map((url) => {
return await httpGet(url); // SyntaxError!
;
}) }
理由は、通常の矢印関数は本体内でawait
を許可しないためです。
では、非同期矢印関数を試してみましょう。
async function downloadContent(urls) {
return urls.map(async (url) => {
return await httpGet(url);
;
}) }
しかし、これも機能しません。.map()
(したがってdownloadContent()
)は、(ラップされていない)値を含む配列ではなく、Promiseを含む配列を返します。
1つの可能な解決策は、Promise.all()
を使用してすべてのPromiseをアンラップすることです。
async function downloadContent(urls) {
const promiseArray = urls.map(async (url) => {
return await httpGet(url); // (A)
;
})return await Promise.all(promiseArray);
}
このコードを改善できますか?はい、できます。A行では、await
を介してPromiseをアンラップし、すぐにreturn
を介して再ラップしています。await
を省略すると、非同期矢印関数も必要なくなります。
async function downloadContent(urls) {
const promiseArray = urls.map(
=> httpGet(url));
url return await Promise.all(promiseArray); // (B)
}
同じ理由で、B行でもawait
を省略できます。
await
の使用 [ES2022]モジュールの最上位レベルでawait
を使用できます。たとえば、
let lodash;
try {
= await import('https://primary.example.com/lodash');
lodash catch {
} = await import('https://secondary.example.com/lodash');
lodash }
この機能の詳細については、27.14章「モジュールでの最上位レベルのawait
[ES2022]」を参照してください。
演習:非同期的なマッピングとフィルタリング
exercises/async-functions/map_async_test.mjs
残りのセクションはすべて上級者向けです。
await
次の2つの小節では、ヘルパー関数paused()
を使用します。
/**
* Resolves after `ms` milliseconds
*/
function delay(ms) {
return new Promise((resolve, _reject) => {
setTimeout(resolve, ms);
;
})
}async function paused(id) {
console.log('START ' + id);
await delay(10); // pause
console.log('END ' + id);
return id;
}
await
:非同期関数を順次実行する複数の非同期関数の呼び出しの前にawait
を付ける場合、これらの関数は順次実行されます。
async function sequentialAwait() {
const result1 = await paused('first');
.equal(result1, 'first');
assert
const result2 = await paused('second');
.equal(result2, 'second');
assert
}
// Output:
// 'START first'
// 'END first'
// 'START second'
// 'END second'
つまり、paused('second')
は、paused('first')
が完全に終了した後にのみ開始されます。
await
:非同期関数を並行して実行する複数の関数を同時に実行したい場合は、ツールメソッドPromise.all()
を使用できます。
async function concurrentPromiseAll() {
const result = await Promise.all([
paused('first'), paused('second')
;
]).deepEqual(result, ['first', 'second']);
assert
}
// Output:
// 'START first'
// 'START second'
// 'END first'
// 'END second'
ここでは、両方の非同期関数が同時に開始されます。両方が解決されると、await
は解決値の配列、または少なくとも1つのPromiseが拒否された場合は例外を返します。
40.6.2節「並行処理のヒント:操作の開始時期に注目する」から、重要なのはPromiseベースの計算を開始するタイミングであり、結果を処理する方法ではないことを思い出してください。したがって、次のコードは前のコードと同じくらい「並行」です。
async function concurrentAwait() {
const resultPromise1 = paused('first');
const resultPromise2 = paused('second');
.equal(await resultPromise1, 'first');
assert.equal(await resultPromise2, 'second');
assert
}// Output:
// 'START first'
// 'START second'
// 'END first'
// 'END second'
await
は不要Promiseベースの関数を使用する場合は、await
は必要ありません。返されたPromiseが解決されるまで一時停止して待機したい場合にのみ必要です。非同期操作を開始したいだけの場合、それは必要ありません。
async function asyncFunc() {
const writer = openFile('someFile.txt');
.write('hello'); // don’t wait
writer.write('world'); // don’t wait
writerawait writer.close(); // wait for file to close
}
このコードでは、終了するタイミングを気にしないため、.write()
を待機しません。ただし、.close()
が完了するまで待機する必要があります。
注:.write()
の各呼び出しは同期的に開始されます。これにより、競合状態が防止されます。
await
して結果を無視しても意味がある場合がある結果を無視する場合でも、await
を使用する方が理にかなう場合があります。たとえば、
await longRunningAsyncOperation();
console.log('Done!');
ここでは、await
を使用して長時間実行される非同期操作を結合しています。これにより、ロギングがその操作が完了した後に確実に実行されます。