coとthunkifyで書いていた非同期処理のフローをasync/awaitで書き換える

要約

タイトルの通りasync/awaitで書き換え可能だしAsync Functionはアロー関数でも定義できるしいい感じです。

coとthunkifyではどうだったか

Node.jsアプリケーションを書いていて、非同期処理の逐次実行が必要なとき、今まではcoとthunkifyを使って書いていた。 具体的にどんな場面だったかというと、requestモジュールを使ってAPIを叩き、その結果を使って別のAPIを叩き……みたいな状況だ。 従来は、Node.jsでの非同期処理を、coとthunkifyを使って幸せに書こうを参考に、以下のようなコードを書いていた。

const co = require('co');
const thunkify = require('thunkify');
const request = thunkify(require('request'));

function doSomething() {
    const headers = { 'Content-Type': 'Application/json' }
    co(function* (){
        const option1 = { headers, url: 'http://example.com/api/first' };
        const reqponse1 = yield request(option1);
        
        const option2 = {
            headers,
            url: 'http://example.com/api/second',
            method: 'POST',
            body: JSON.stringify({ state: response1[1].state })
        };
        const response2 = yield request(option2);
    }).catch((err) => console.log(err));
}

thunkifyによってthunkに変換されたrequestをcoがPromiseに変換し、ジェネレータとの組み合わせによってAPIリクエストのレスポンスを待つことができるようになっている。 このように言語化するとthunkifyやcoのやっていることが余計に感じられ、素のgeneratorとPromiseでもいいじゃんと思われた。そこで、せっかくなのでES2017で追加されたasync/awaitを使って書き直してみた。

async/awaitによる書き換え

const request = require('request-promise-native');

async function doSomething() {
    const headers = { 'Content-Type': 'Application/json' };
    try {
        const option1 = { headers, url: 'http://example.com/api/first' };
        const responseBody1 = await request(option1);
        
        const option2 = {
            headers,
            url: 'http://example.com/api/second',
            method: 'post',
            body: JSON.stringify({ state: responseBody1.state })
        };
        const responseBody2 = await request(option2);
    } catch (err) {
        console.log(err);
    }
}

requestの代わりにrequest-promise-nativeを使う。request-promise-nativeのrequest()はPromiseを返し、await式によってPromiseがresolveされるまでdoSomething()の実行は停止される。Promiseがrejectされたら外のtry-catchで捕捉できる。 多少見通しがよくなったぞと思って改めてMDNのAsync Function の解説を見ると、

async/await 関数の目的は、 promise を同期的に使用する動作を簡素化し、 Promise のグループに対して何らかの動作を実行することです。 Promise が構造化コールバックに似ているのと同様に、 async/await はジェネレータと promise を組み合わせたものに似ています。

とあり、あっハイその通りですといった記載があった。

Async FunctionをExpressのルーティングに使う

Async FunctionをExpressのルーティングのハンドラーに使うと、リクエストを起点に非同期処理を順番に実行するといったコードがシンプルになる。 これは上記のdoSomething()のようにAPIを順番に叩く例。

const express = require('express');
const router = express.Router();
const request = require('request-promise-native');

router.get('/something', async (req, res) => {
    const headers = { 'Content-Type': 'Application/json' }
    try {
        const option1 = { headers, url: 'http://example.com/api/first' };
        const responseBody1 = await request(option1);
        
        const option2 = {
            headers,
            url: 'http://example.com/api/second',
            method: 'post',
            body: JSON.stringify({ state: responseBody1.state })
        };
        const responseBody2 = await request(option2);
        res.send();
    } catch (err) {
        console.log(err);
        res.status(500).send();
    }
});

アロー関数を使ってasync () => {}とも書けるのがいい。