Skip to content

Commit

Permalink
feat(Ch5): Async Functionの章
Browse files Browse the repository at this point in the history
  • Loading branch information
azu committed May 6, 2019
1 parent 78993fb commit b3d81db
Show file tree
Hide file tree
Showing 7 changed files with 591 additions and 1 deletion.
1 change: 0 additions & 1 deletion Ch4_AdvancedPromises/readme.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
// promiseとレビュー
include::promise-library.adoc[]


// Promise.resolveとthenable
include::resolve-thenable.adoc[]

Expand Down
118 changes: 118 additions & 0 deletions Ch5_AsyncFunction/async-function-await.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
[async-function-await]
== ``await``

Async Functionのことは一般的にasync/awaitとも呼ばれることがあります。
この呼ばれ方からもわかるようにAsync Functionにおける``await``式は重要な役割を持っています。

``await``式はAsync Functionの関数内でのみ利用できます。
``await``式は右辺の``Promise``インスタンスが**Fulfilled**または**Rejected**になるまでその場で非同期処理の完了を待ちます。
そして``Promise``インスタンスの状態が変わると、処理を再開します。


[source,js]
----
async function asyncMain() {
// PromiseがFulfilledまたはRejectedとなるまで待つ
await Promiseインスタンス;
// Promiseインスタンスの状態が変わったら処理を再開する
}
----

普通の処理の流れでは、非同期処理を実行した場合にその非同期処理の完了を待つことなく、次の行(次の文)を実行します。
しかし``await``式では非同期処理を実行し完了するまで、次の行(次の文)を実行しません。
そのため``await``式を使うことで非同期処理が同期処理のように上から下へと順番に実行するような処理順で書けます。


[source,js]
----
// async functionは必ずPromiseを返す
async function doAsync() {
// 非同期処理
}
async function asyncMain() {
// doAsyncの非同期処理が完了するまでまつ
await doAsync();
// 次の行はdoAsyncの非同期処理が完了されるまで実行されない
console.log("この行は非同期処理が完了後に実行される");
}
----

``await``式は**式**であるため右辺(``Promise``インスタンス)の評価結果を値として返します。
この``await``式の評価方法は評価するPromiseの状態(**Fulfilled**または**Rejected**)によって異なります。

``await``式の右辺のPromiseが**Fulfilled**となった場合は、resolveされた値が``await``式の返り値となります。

次のコードでは、``await``式の右辺にある``Promise``インスタンスは``42``という値でresolveされています。
そのため``await``式の返り値は``42``となり、``value``変数にもその値が入ります。

[role="executable"]
[source,javascript]
----
async function asyncMain() {
const value = await Promise.resolve(42);
console.log(value); // => 42
}
asyncMain(); // Promiseインスタンスを返す
----

これはPromiseを使って書くと次のコードと同様の意味となります。
``await``式を使うことでコールバック関数を使わずに非同期処理の流れを表現できていることがわかります。

[role="executable"]
[source,javascript]
----
function asyncMain() {
return Promise.resolve(42).then(value => {
console.log(value); // => 42
});
}
asyncMain(); // Promiseインスタンスを返す
----

``await``式の右辺のPromiseが**Rejected**となった場合は、その場でエラーを``throw``します。
またAsync Function内で発生した例外は自動的にキャッチされます。
そのため``await``式でPromiseが**Rejected**となった場合は、そのAsync Functionが**Rejected**なPromiseを返すことになります。

次のコードでは、``await``式の右辺にある``Promise``インスタンスが**Rejected**の状態になっています。
そのため``await``式は``エラー``を``throw``するため、``asyncMain``関数は**Rejected**なPromiseを返します。

[role="executable"]
[source,javascript]
----
async function asyncMain() {
const value = await Promise.reject(new Error("エラーメッセージ"));
// await式で例外が発生したため、この行は実行されません
}
// Async Functionは自動的に例外をキャッチできる
asyncMain().catch(error => {
console.log(error.message); // => "エラーメッセージ"
});
----

``await``式がエラーを``throw``するということは、そのエラーは``try...catch``構文でキャッチできます。
通常の非同期処理では完了する前に次の行が実行されてしまうため``try...catch``構文ではエラーをキャッチできませんでした。
そのためPromiseでは``catch``メソッドを使いPromise内で発生したエラーをキャッチしていました。(<<promise-done,Promise.prototype.done とは何か?>>を参照)

次のコードでは、``await``式で発生した例外を``try...catch``構文でキャッチしています。

{{book.console}}
// doctest:async:16

[source,js]
----
async function asyncMain() {
// await式のエラーはtry...catchできる
try {
const value = await Promise.reject(new Error("エラーメッセージ"));
// await式で例外が発生したため、この行は実行されません
} catch (error) {
console.log(error.message); // => "エラーメッセージ"
}
}
asyncMain().catch(error => {
// すでにtry...catchされているため、この行は実行されません
});
----

このように``await``式を使うことで、``try...catch``構文のように非同期処理を同期処理と同じ構文を使って扱えます。
またコードの見た目も同期処理と同じように、その行(その文)の処理が完了するまで次の行を評価しないという分かりやすい形になるのは大きな利点です。
71 changes: 71 additions & 0 deletions Ch5_AsyncFunction/async-function-syntax.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
[async-function-syntax]
== Async Functionの構文

Async Functionは関数の定義に``async``キーワードをつけることで定義できます。
JavaScriptの関数定義には関数宣言や関数式、Arrow Function、メソッドの短縮記法などがあります。
どの定義方法でも``async``キーワードを前につけるだけでAsync Functionとして定義できます。

[role="executable"]
[source,javascript]
----
// 関数宣言のAsync Function版
async function fn1() {}
// 関数式のAsync Function版
const fn2 = async function() {};
// Arrow FunctionのAsync Function版
const fn3 = async() => {};
// メソッドの短縮記法のAsync Function版
const object = { async method() {} };
----

これらのAsync Functionは、必ずPromiseを返すことや関数中では``await``式が利用できること以外は、通常の関数と同じ性質を持ちます。

[#async-function-return-promise]
== Async FunctionはPromiseを返す

Async Functionとして定義した関数は必ず``Promise``インスタンスを返します。
具体的にはAsync Functionが返す値は次の3つのケースが考えられます。

1. Async Functionは値をreturnした場合、その返り値をもつ**Fulfilled**なPromiseを返す
2. Async FunctionがPromiseをreturnした場合、その返り値のPromiseをそのまま返す
3. Async Function内で例外が発生した場合は、そのエラーをもつ**Rejected**なPromiseを返す

次のコードでは、Async Functionがそれぞれの返り値によってどのような``Promise``インスタンスを返すかを確認できます。
これらの挙動は``Promise#then``メソッドの返り値とそのコールバック関数が返す値の関係とほぼ同じです。

[role="executable"]
[source,javascript]
----
// 1. resolveFnは値を返している
// 何もreturnしていない場合はundefinedを返したのと同じ扱いとなる
async function resolveFn() {
return "返り値";
}
resolveFn().then(value => {
console.log(value); // => "返り値"
});
// 2. rejectFnはPromiseインスタンスを返している
async function rejectFn() {
return Promise.reject(new Error("エラーメッセージ"));
}
// rejectFnはRejectedなPromiseを返すのでcatchできる
rejectFn().catch(error => {
console.log(error.message); // => "エラーメッセージ"
});
// 3. exceptionFnは例外を投げている
async function exceptionFn() {
throw new Error("例外が発生しました");
// 例外が発生したため、この行は実行されません
}
// Async Functionで例外が発生するとRejectedなPromiseが返される
exceptionFn().catch(error => {
console.log(error.message); // => "例外が発生しました"
});
----

どの場合でもAsync Functionは必ずPromiseを返すことがわかります。
このようにAsync Functionを呼び出す側から見れば、Async FunctionはPromiseを返すただの関数と何も変わりません。
105 changes: 105 additions & 0 deletions Ch5_AsyncFunction/async-function.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
[[async-function]]
== Async Functionとは

Async Functionとは非同期処理を行う関数を定義する構文です。
Async Functionは通常の関数とは異なり、必ず``Promise``インスタンスを返す関数を定義する構文です。

Async Functionは次のように関数の前に``async``をつけることで定義できます。
この``doAsync``関数は常に``Promise``インスタンスを返します。

[role="executable"]
[source,javascript]
----
async function doAsync() {
return "値";
}
// doAsync関数はPromiseを返す
doAsync().then(value => {
console.log(value); // => "値"
});
----

Async Functionでは``return``した値の代わりに、``Promise.resolve(返り値)``のように返り値をラップした``Promise``インスタンスを返します。
そのため、このAsync Functionは次のように書いた場合と同じ意味になります。

[role="executable"]
[source,javascript]
----
// 通常の関数でPromiseインスタンスを返している
function doAsync() {
return Promise.resolve("値");
}
doAsync().then(value => {
console.log(value); // => "値"
});
----

またAsync Function内では``await``式というPromiseの非同期処理が完了するまで待つ構文が利用できます。
``await``式を使うことで非同期処理を同期処理のように扱えるため、Promiseチェーンで実現していた処理の流れを読みやすくかけます。

まずは、Async Functionと``await``式を使った場合はどのように書けるかを簡単に見ていきます。

次の例ではFetch APIで``/json/book.json``を取得して、``title``を取り出す``getBookTitle``関数の実行結果をコンソールに出力しています。
取得``book.json``は次のような内容になっています。

[[book.json]]
./json/book.json
[source,json]
----
include::../json/book.json[]
----

次のコードでは、Promiseのみを使って`getBookTitle``関数で取得したタイトルをコンソールに出力しています。

[role="executable"]
[source,javascript]
----
function getBookTitle(){
return fetch("/json/book.json").then(function(res){
return res.json(); // レスポンスをJSON形式としてパースする
}).then(json => {
return json.title; // JSONからtitleプロパティを取り出す
});
}
function main(){
getBookTitle().then(function(title) => {
console.log(title); // => "JavaScript Promiseの本"
});
}
main();
----

次のコードでは、Async Functionと``await``式を使って`getBookTitle``関数で取得したタイトルをコンソールに出力しています。

[role="executable"]
[source,javascript]
----
function getBookTitle(){
return fetch("/json/book.json").then(function(res){
return res.json(); // レスポンスをJSON形式としてパースする
}).then(json => {
return json.title; // JSONからtitleプロパティを取り出す
});
}
// `async`をつけてAsync Functionを定義
async function main(){
// `await`式で`getBookTitle`の非同期処理が完了するまで待つ
// `getBookTitle`がresolveした値が返り値になる
const title = await getBookTitle();
console.log(title); // => "JavaScript Promiseの本"
}
main();
----

Async Functionでは非同期処理が完了するまで待つ``await``式を使うことができます。
これにより、Promiseでは結果を``then``メソッドのコールバック関数で取得していたのが、``await``式によって同期的な関数のように結果を受け取れます。
このように、Async Functionと``await``式を使うことで非同期処理をまるで同期処理のように書けます。

この章では、Async Functionと``await``式について見ていきます。

重要なこととしてAsync FunctionはPromiseの上に作られた構文です。
そのためAsync Functionを理解するには、Promiseを理解する必要があることに注意してください。
Loading

0 comments on commit b3d81db

Please sign in to comment.