近頃ようやくasync/awaitを理解した。巷の例はむやみに複雑なので、極力単純化して説明する。
同期関数
以下のプログラムは標準入力の内容を ** start **
と *** end ***
に挟んで出すだけの処理である。同期関数 の readFileSync()
を使っている。
echo0.js
const fs = require('fs');
console.log('** start **');
console.log(fs.readFileSync(0, 'utf-8'));
console.log('*** end ***');
$ echo -n Hello | node echo0.js
** start **
Hello
*** end ***
コールバック
readFileSync()
の代わりに非同期の readFile()
を使わざるを得ないと仮定する。コールバック で処理すると以下のプログラムになる。
echo1.js
const fs = require('fs');
function getstdin(callback) {
fs.readFile(0, 'utf-8', (err, data)=>callback(data))
}
console.log('** start **');
getstdin(console.log);
console.log('*** end ***');
当然ながら期待した結果にはならない。
$ echo -n Hello | node echo1.js
** start **
*** end ***
Hello
Promise
コールバックを Promise に置き換える。
echo2.js
const fs = require('fs');
function getstdin() {
return new Promise(resolve=>{
fs.readFile(0, 'utf-8', (err, data)=>resolve(data))
});
}
console.log('** start **');
getstdin().then(data=>console.log(data));
console.log('*** end ***');
やはり結果は同じだ。
$ echo -n Hello | node echo2.js
** start **
*** end ***
Hello
async/await
async/await を使うと以下になる。
echo3.js
const fs = require('fs');
function getstdin() {
return new Promise(resolve=>{
fs.readFile(0, 'utf-8', (err, data)=>resolve(data))
});
}
(async ()=>{
console.log('** start **');
console.log(await getstdin());
console.log('*** end ***');
})();
- Promise を await つきで呼び出すと同期処理となり、.then() で受け取るべき値を戻り値として受け取れるようになる。
- await は async 宣言された関数内でしか使うことはできない。
上記の例では await を使うためだけに async 宣言した無名即時関数で囲っている。
結果は期待通り。OK!
$ echo -n Hello | node echo3.js
** start **
Hello
*** end ***
まとめ
- Promise で記述された非同期処理を同期的に扱いたい場合は、その呼び出しに await をつけることで .then() で非同期に受け取るべき値を返り値として同期的に受け取れるようになる。
- その代償として await を囲む関数は非同期となる。そのことを明示的に示すために宣言に async をつけなければならない。
- async 宣言された関数は非同期となるので return で値は返せなくなる。return を使っている場合は呼出し元ではその関数が Promise であるかのごとく .then() で受け取らなくてはならない。