Posted at

年末休みにnodeでAPIサーバを勉強した復習〜part4 async/awaitの説明


おおまかな非同期説明


callback hell

nodeは非同期フレームワークである

非同期とはなにかというのを全て説明するのは面倒なので調べて欲しいが

I/Oの時間のかかる処理を待たずに遅延実行する事によりCPUを有効に使う方法だ

I/Oの処理が終わったら実行を継続するため、次の処理をコールバック関数で渡す事になる

例えば

var fs = require('fs');

fs.readFile('hoge.txt', 'utf8', function (err, text) {
if(err){
// エラー処理
else{
// 正常処理
}
...
});

のように、引数の最後にあるのが読み込み終了時のコールバックだ

これで、I/Oを待たずCPUは次の処理を行い、I/Oから戻ってきたらコールバックが処理され

非同期に実行できるようになった

しかしこのままでは callback hellになる

http://callbackhell.com/

上記はぬるいHellだが、私はnode初期の頃に恐ろしい地獄を作ったこともある

callback hellは個人的には楽しいが、色々な問題に直面する

実行順がぱっと見でわかりにくい

例外の扱いが難しい

デバッグが難しい

などがあるため、promise/futureや async/awaitを使うべきだ

function hogeAsync(msg, callback){

console.log(msg);

if(callback!=null) callback();
}

hogeAsync("1", hogeAsync("2", hogeAsync("3")));

(console.logはそんな時間かからないが サンプルなのでそんなかんじで)

期待する結果は?

答えは 321 だ!

更にエラー処理等が入ると実行順序はますますわかりにくくなる


promise/future

昔のAWS Lambdaでは async/awaitが使えなかったはずで

その時にpromise/futureを使っていた

promise/futureでは、非同期処理をメソッドチェーンで同期的につなげることができる

callback hellと違い、実行順もイメージ通りになる

function hogeAsync(msg){

const promise = new Promise((resolve, reject) => {
console.log(msg);
resolve();
});
return promise;
}

hogeAsync("1")
.then(()=>hogeAsync("2"))
.then(()=>hogeAsync("3"))

結果は期待通り、123だ


async/await

promise/futureでも非同期処理を同期的に扱うことが出来たが

メソッドチェーンでつなぐため、非同期のネストが深くなったり

間に制御構文を入れた場合にも読みにくくなるし

例外処理が面倒だ

そこでasync/awaitを使う

実はasync/awaitは内部はpromiseである

promiseにかぶせて、より同期的に見せているだけである

async/awaitはメソッドチェーンで繋がず、文を終わらせるためネストが深くなり辛い

また、例外を全部同じところで取れるし

エラー時のスタックトレースも比較的楽になる

そのため、現時点では async/awaitがベストだと思う

promise/futureとasync/awaitの比較

https://qiita.com/Anders/items/dfcb48d8b27ceaffb443

function hogeAsync(msg){

const promise = new Promise((resolve, reject) => {
console.log(msg);
resolve();
});
return promise;
}

async function hoge(){
await hogeAsync("1");
await hogeAsync("2");
await hogeAsync("3");
};
hoge();

当然結果は 123

また、async属性を関数につければ、自動的にpromiseを生成しreturnするので

下記のように省略できる

async function hogeAsync(msg){

console.log(msg);
}

(async () => {
await hogeAsync("1");
await hogeAsync("2");
await hogeAsync("3");
})();


async/awaitの説明

結局Promiseを見えなくして使ってるだけなのですよ

説明はググって


複数の非同期関数を同期させる

Promiseではallを使うと、複数の非同期関数をまとめて同期出来たが

async/awaitでも同じものを使う

Promise.all

https://qiita.com/im36-123/items/c0678a46ee0f8e44e150


expressの場合

nodeでAPIサーバに使われるExpressフレームワークはなんとPromiseに対応されていない

が、コールバック地獄は勘弁

なので 下記のように少しでも使いやすくする

expressで非同期するときのパターン

https://qiita.com/yukin01/items/1a36606439123525dc6d

https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016

同じく

http://www.acuriousanimal.com/2018/02/15/express-async-middleware.html

つまり、node-async-handlerを使って、例外はnextに投げておけば、とりあえずの所はちゃんと動く


asynchell

https://qiita.com/rana_kualu/items/e6c5c0e4f60b0d18799d

async/await使っても Hellになる事ある

とりあえず、関数の依存関係は考えることと

promise.all がとても便利ってこと