async/await
async 読み方:エイシンク(アシンクではない)
await 読み方:アウェイト(エイウェイトではない)
async は関数が非同期であること(=awaitを含む)を示す、修飾子となる。(糖衣構文)
単純に手続き型のように書くと、Promise を返す関数に自動的に置き換えてくれる。(これが理解しづらい)
async の関数を呼び出すときには、
async 関数内では await 付きで実行する必要がある。
通常の同期関数で実行する場合は、Promise が戻ってくるので、then と catch メソッドを使う必要がある。
自力で Promise を返す関数には async を付けてはならない。(要注意)
await は async 関数(または Promise を返す関数)を呼び出すことを示す演算子となる。
トップレベルで単純に async 関数を実行したときにはどうなるのか?
即時実行され、Promise オブジェクトが返ってくる。
code:sample1.js
function timeout(ms) {
return new Promise( resolve => setTimeout(resolve, ms) );
}
async function helloTimeout() {
var r = "Hello";
console.log(r);
await timeout(3000);
r += ", world!";
console.log("inside:" + r);
return r;
}
var r = helloTimeout();
console.log("outside:" + r);
Hello
inside:Hello, world!
トップレベルで await 付きで async 関数を実行したときにはどうなるのか?
"SyntaxError: await is only valid in async function" と言われて実行不能。(ES2015の時点)
code:sample2.js
// 共通部は除外
var r = await helloTimeout();
console.log("outside:" + r);
SyntaxError: await is only valid in async function
同期型のメソッドの中で async 関数を実行したときにはどうなるのか?
即時実行され、Promise オブジェクトが返ってくる。
code:sample3.js
// 共通部は除外
function myHelloSync() {
var r = helloTimeout();
console.log("outside:" + r);
}
myHelloSync();
Hello
inside:Hello, world!
同期型のメソッドの中で await 付きで async 関数を実行したときにはどうなるのか?
"SyntaxError: await is only valid in async function" と言われて実行不能。
code:sample4.js
// 共通部は除外
function myHelloSync() {
var r = await helloTimeout();
console.log("outside:" + r);
}
myHelloSync();
SyntaxError: await is only valid in async function
async 関数なのに await を持たない場合はどうなるのか?
特に気にしなくてもよい。(ESLint は警告を出すが、問題なく実行される。)
どうしても await を入れたい場合は、await Promise.resolve(value); とすることで、問題なくなる。(value は返したい値。なくてもよい。)
関数を呼んだときには、内部処理はすべて即時実行される。
関数は Promise を返す。(この Promise は resolve 呼び出し済みとなる。)
内部で await なしで async 関数を呼んでいる場合は、その async 関数は Promise を返す。(結果の Promise とは無関係)
3種類の方式が提案されている。
async による即時関数で実行させる方法。
code:sample5.js
(async () => {
try {
var r = await helloTimeout();
console.log("outside:" + r);
} catch (err) {
// 例外時の処理
}
})();
code:sample6.js
(async () => {
var r = await helloTimeout();
console.log("outside:" + r);
})().cache(err => {
// 例外時の処理
});
返ってくるのは Promise なので、then, catch でトラップする方法。
code:sample7.js
helloTimeout().then(text => {
console.log("outside:" + text);
}).catch(err => {
// 例外時の処理
});
await をそのまま書く方法。(ただしモジュール内に限る)
setTimeout を async/await で扱えるようにする
code:timeout.js
function timeout(ms) {
return new Promise( resolve => setTimeout(resolve, ms) );
}
util.promisify を使うと、同期関数を非同期関数にできる。
code:promisify.js
import util from "util";
import { execFile } from "child_process";
const execFileAsync = util.promisify( execFile );
...
async function execSampleExt() {
const params = Array.from(arguments);
const { stdout } = await execFileAsync("sample_ext", params);
return stdout;
}
同期型の関数に非同期型の関数を引き渡して、部分的に非同期にしてその値を使うことは可能か?
code:pseudo.js
function timeout(ms) {
return new Promise( resolve => setTimeout(resolve, ms) );
}
async myasync() {
var r = 1;
await timeout(3000);
r += 1;
return r;
}
function mysync(f) {
var r = f() + 1; // ここが非同期になって欲しい
return r;
}
console.log(mysync(myasync));
今の所不可能。
非同期の関数は同期間数から呼ぶと Promise を返して終了してしまう。
Promise.all, Promise.any 自体も Promise を返り値として、すぐに終了してしまう。
Promise に対して then でコールバック関数を渡しても、Promise を返り値として、すぐに終了してしまう。
Promise オブジェクトの値の変化をループでポーリングしたくても、シングルスレッドで無限ループすることになる。脱出しない限り、肝心の Promise オブジェクトの状態は変わらない。
Promise オブジェクトの値の変化を setTimeout で定期的に監視しようとしても、コールバック関数を渡すだけで、関数を脱出するしかない。