JavaScriptの async/await の使い方

English follows Japanese. 英語の文章は後半にあります。

こんにちは。ShareWisのソフトウェアエンジニアのフンです。
この記事ではJavascriptにおける async/awaitについて紹介したいと思います。

async/awaitパターンとは多くのプログラミング言語で用いられる構文上の特徴であり、非同期、ノンブロッキング関数を通常の同期関数と同様の方法で構造化することができるようにするものです。

Javascriptにおける async/await とは?

async/await は (ES2017+) リリースで追加された、JavaScriptでPromise*[1]を簡単に書くためのシンタックスシュガー*[2]です。async/awaitを使用することで同期的に見えるJavaScriptのコードを非同期的に動作するように記述することができます。
*[1]. エラー、成功問わず処理が終了したことを伝えてくれる仕組み。
*[2]. 構文を別の記法で記述できるようにしたもの。
https://blog.share-wis.com/js-promises

async とは?

asyncとは任意の関数の前に追加することができるJavascript の予約語の一つです。asyncを関数の前に置くとその関数がPromiseオブジェクトを返すようになります。

async関数を使用するには、以下のように従来のプロミス/コールバックの記法で記述します。

async function getCompanyName () {
    return "Share-Wis Company"
}

明示的にPromiseオブジェクトをを指定することも可能です。下記は上記のコードと同じ値を返します。

async function getCompanyName() {
    return Promise.resolve("Share-Wis Company")
}

getCompanyName()を使用するには関数が返すPromiseオブジェクトを使用します。getCompanyName()の処理が成功すると、then()関数が発火しコールバックが呼び出されます。

getCompanyName().then((res)=> {
    console.log('Company name:',res)
})

実行結果を見てみましょう。

“Company name: Share-Wis Company”が表示されました!

awaitとは?

awaitはasyncと同じJavascriptの予約語の一つで、asyncと宣言された関数ブロックの中で使うことができます。awaitはasyncが使用されてなければ、何の意味ももちません。
awaitを非同期呼び出しの前に置くことで、特定のPromiseの処理が終了するのを待ち、終了を確認した後、非同期ブロックの実行が行われます。

awaitの書き方

let value = await promise;

awaitはasyncが使用されていない関数内で使用できない

もし非同期ではない関数内でawaitを使おうとすると、シンタックスエラーが発生します。

function f() {
    let promise = Promise.resolve(1);
    let result = await promise; // Syntax error
}

awaitは非同期の関数内でのみ動作します。

エラー分を見てみると
上記のシンタックスエラーが出てるコードを修正すると下記のようになります。
async function getCompanyName () {
    return "Share-Wis Company"
}
async function callingFunction() {
    const companyName = await getCompanyName() 
    console.log('Company name:',companyName) 
}
callingFunction()
実行結果を見ると
“Company name: Share-Wis Company”が無事表示されました!
awaitは、asyncブロックの処理が終了するのを待つだけで、他の処理を止めたりはしません。

async/awaitはPromise.allとの相性が抜群

複数のPromiseの結果が返ってくるのを待つ必要がある場合、下記のようにawaitPromise.allで関数を囲ってやることで複数の処理を記述することができます。
let results = await Promise.all([
   fetch(url1),
   fetch(url2),
    ...
]);

エラーが発生した場合は、失敗したPromiseからPromise.allに通常通り伝搬され例外となるので、try…catchを使って呼び出しの周りでキャッチすることができます。

async/awaitブロック内でのエラーの取り扱い

Promiseの処理が正常に実行された場合、await Promiseは結果を返します。しかし、異常が発生し途中で終了した場合は、そのエラーとなった行からthrow文が投げられるので、エラーが発生したら
async function handlingError() {
    await Promise.reject(new Error('error'))
}

または、

async function handlingError() {
    throw new Error("error");
}
のように書くことでエラー受けとることができます。
awaitはPromiseと異なり、エラーに対するcatch/errorブロックのような特別な処理機能を持ちません。以下のように、try/catch文でawaitを囲む必要があります。
async function handlingError() {
    try {
        const response = await fetch('api-url')
    } catch(err) {
        console.log(err)
    }
}
If we don’t have try..catch, then the promise generated by the call of the async function handlingError() becomes rejected. We can append .catch to handle it:
try..catchを記述しないと、async関数であるhandlingError()で例外発生時に生成されるPromiseが拒否されエラーとなります。その場合は.catchを関数呼び出し時に付け加えて対処する必要があります。
async function handlingError() {
    const response = await fetch('api-url')
}
// handlingError() 例外処理発生時にPromiseが拒否される
handlingError().catch(alert)

.catch をつけ忘れると例外処理発生時に、上記画像の様にhandlingErrorが発生します。

最後に

awaitを関数の前に置くことで出来るようになること

    1. 常にPromiseオブジェクトをreturnで返すようになります。
    2. その関数ブロック内でawaitが使用できるようになります。

Promiseの前にawaitを置くことで、その処理が終了するまで待ち、終了を確認した後に次の処理へと移ります

    1. エラーが発生すれば、そのエラーになった行からawaitを設置したPromiseに対してthrow errorが投げられる。
    2. 処理が正常に終了した場合は、結果を返却する。

async/await を使用するメリット

  1. コードの可読性があがる
    Promiseは大変便利なのですが、Promise単体で使用することで処理が数珠つなぎとなり、コードが煩雑になることが多いです。
  2. コードを簡潔に書くことができる
    処理ごとに関数を書くので、コードの再利用性が高くなります。
  3.  デバッグが容易になる
    各々の処理をブロック単位で書くことが出来るようになるので、Promise単体で使用するより場合に比べ、問題発生時にバグが発見しやすくなります。

(フン)


English version: How to use async/await of JavaScript

In computer programming, the async/await pattern is a syntactic feature of many programming languages that allows an asynchronous, non-blocking function to be structured in a way similar to an ordinary synchronous function.

What is async/await in Javascript?

async/await was added in the (ES2017+) release, it is syntactic sugar that makes it easier to write promises in JavaScript. async/await helps you write synchronous-looking JavaScript code that works asynchronously.

What is async?

async is a keyword that can be added before any function. Once it is placed before a function, it makes sure that the function returns a promise.

In order to use the async function, you will have to resolve it using the traditional promise/callback approach as depicted in the code below:

async function getCompanyName () {
    return "Share-Wis Company"
}

We could explicitly return a promise, which would be the same:

async function getCompanyName() {
    return Promise.resolve("Share-Wis Company")
}

Use the promise object returned by the function.The then() function, success callback is invoked once the promise resolved.

getCompanyName().then((res)=> {
    console.log('Company name:',res)
})

Let see the output blow:

What is await?

await is another keyword in JavaScript which can be used in a block declared as async. await has no meaning or existence without async.
Once you place await before any asynchronous call, it will wait for that particular promise to settle and then further execution from the async block will take place.

The syntax

let value = await promise;

Can’t use await in regular functions

If we try to use await in a non-async function, there would be a syntax error:

function f() {
    let promise = Promise.resolve(1);
    let result = await promise; // Syntax error
}

await only works inside an async function

The error will show

Let improve the above example by using await

async function getCompanyName () {
    return "Share-Wis Company"
}
async function callingFunction() {
    const companyName = await getCompanyName() 
    console.log('Company name:',companyName) 
}
callingFunction()

The output:

await only makes sure an async block waits. it does not stop Javascript from executing other operations.

async/await works well with Promise.all

When we need to wait for multiple promises, we can wrap them in Promise.all and then await:

let results = await Promise.all([
   fetch(url1),
   fetch(url2),
    ...
]);

In the case of an error, it propagates as usual, from the failed promise to Promise.all, and then becomes an exception that we can catch using try..catch around the call.

How can we handle errors in async/await block?

If a promise resolves normally, then await promise returns the result. But in the case of a rejection, it throws the error, just as if there were a throw statement at that line.

async function handlingError() {
    await Promise.reject(new Error('error'))
}

or use this:

async function handlingError() {
    throw new Error("error");
}

Unlike promise, await does not have special handling features like catch/error block for the errors. You have to use try/catch block over the await statements as shown below.

async function handlingError() {
    try {
        const response = await fetch('api-url')
    } catch(err) {
        console.log(err)
    }
}

If we don’t have try..catch, then the promise generated by the call of the async function handlingError() becomes rejected. We can append .catch to handle it:

async function handlingError() {
    const response = await fetch('api-url')
}
// handlingError() becomes a rejected promise
handlingError().catch(alert)

If we forget to add .catch there, then we get an unhandled promise error (viewable in the console).

Conclusion

The async keyword before a function has two effects

    1. Makes it always return a promise.
    2. Allows await to be used in it.

The await keyword before a promise makes JavaScript wait until that promise settles, and then

    1. If it’s an error, an exception is generated — same as if throw error were called at that very place.
    2. Otherwise, it returns the result.

async/await brings the following advantages

    1. Code Readability
      promise chain or nested callbacks lead to a lot of confusion. The code becomes less readable.
    2. Concise and clean code
      async/await have concise and clean codes and do the same thing
    3.  Debugging:
      As code is not divided into inner callbacks, developers can find it easy to go through lines and debug the code.

(Hung)

採用情報

株式会社シェアウィズでは、新しいメンバーの募集を積極的に行っています。フルタイムの勤務から学生インターンまで、ご関心をお持ちの方はお気軽にコンタクトしてください!

採用ページへ

開発
ShareWis Blog(シェアウィズ ブログ)