やりたいこと
async/await
とPromise
が全然理解できていないと思ったため、よくわからないと思ったところを実験してみることにしました。
この記事はその実験結果をまとめたものです。
この実験をしたのが記事の執筆時から1ヶ月くらい前の話なので、記憶が朧げなところがあります。
できる限り思い出して書いていますが、間違いがあったらすみません。
実験方法
以下の4つの関数を使って実験をします。
// Promiseを書いたasync
async function a() {
return new Promise((resolve, reject) => {setTimeout(() => {
console.log('aが実行');
console.timeLog();
resolve();
},1000);})
}
// Promiseを返すasyncじゃない関数
function b() {
return new Promise((resolve, reject) => {setTimeout(() => {
console.log('bが実行');
console.timeLog();
resolve();
},1500);})
}
// Promiseを書いていないasync
async function c() {
setTimeout(() => {
console.log('cが実行');
console.timeLog();
},1700);
}
// 謎の関数X
function x() {
console.log('xが実行');
console.timeLog();
}
それぞれの関数の特徴は以下の通りです。
関数名 | ログ出力までの時間 | asyncはあるか | Promiseを返すか |
---|---|---|---|
a | 1000ミリ秒 | ある | 返す |
b | 1500ミリ秒 | ない | 返す |
c | 1700ミリ秒 | ある | 返さない |
x | 0ミリ秒 | ない | 返さない |
上の関数たちをawait
をつけたりthen
を使って実行してみたりします。
そしてその結果を記録していきます。
実験結果
上の関数をいろいろ実行してみた結果です。
長いので結論だけ読みたい方は飛ばしてください。
>
は返り値を、time:数値
は前のログが出力されてから経過した時間を示しています。
特に何も考えず実験結果を常体で書いてしまったため、
ここから敬語じゃ無くなります。
何もつけなかった場合
a
は async
があってPromise
を返す関数。
a();
// > time:0 Promise {<penging>}
// time:1000 aが実行
b
は async
が無くてPromise
を返す関数。
b();
// > time:0 Promise {<penging>}
// time:1500 bが実行
c
は async
があってPromise
は返さない関数。
c();
// > time:0 Promise {<fulfilled>: undefined}
// time:1700 cが実行
x
は async
が無くてPromise
も返さない関数。
x();
// time:0 xが実行
// > time:0 undefined
結果をまとめるとこんな感じ。
関数名 | 返り値 | 値が返ってくるまでの時間 |
---|---|---|
a | Promise {<penging>} |
0ミリ秒 |
b | Promise {<penging>} |
0ミリ秒 |
c | Promise {<fulfilled>: undefined} |
0ミリ秒 |
x | undefined |
0ミリ秒 |
then
やawait
などをつけないで実行した場合、値が返ってくるまでの時間(=処理が終了するまでの時間)はほぼ0。
また、a
とb
は全く同じ結果だった。
c
はa
やb
と返り値が違った。
thenを使った場合
次はa, b, c
の関数にthen
をつけて実行してみる。
then
で実行するのはx
。
a
は async
があってPromise
を返す関数。
a().then(x);
// > time:0 Promise {<penging>};
// time:1000 aが実行
// time:0 xが実行
b
は async
が無くてPromise
を返す関数。
b().then(x);
// > time:0 Promise {<penging>};
// time:1500 bが実行
// time:0 xが実行
c
は async
があってPromise
は返さない関数。
c().then(x);
// time:0 xが実行
// > time:0 Promise {<fulfilled>: undefined}
// time:1700 cが実行
ここまでの結果を表にまとめるとこんな感じ。
関数名 | 返り値 | ログの出力順 |
---|---|---|
a | Promise {<penging>} |
a → x |
b | Promise {<penging>} |
b → x |
c | Promise {<fulfilled>: undefined} |
x → c |
a,b
はこれらの関数が実行し終わってからx
が実行された。
c
は実行し終わる前にx
が実行された。
また、前と同じくa
とb
の実験結果は変わらなかった。
ログの出力順的にc
だけ実行完了を待っていないように思える。
次は複数の関数をthen
で実行してみる。
a().then(b).then(x);
// > time:0 Promise {<penging>};
// time:1000 aが実行
// time:1500 bが実行
// time:0 xが実行
// 実行順: a → b → x
// 上の意味: aが実行し終わった後にbが実行開始され、bが実行し終わってからxが実行開始された
b().then(c).then(x);
// > time:0 Promise {<penging>};
// time;1500 bが実行
// time:0 xが実行
// time:1700 cが実行
// 実行順: b → c = x
// 上の意味: bが実行し終わった後に、cとxが同時に実行開始された
c().then(b).then(x);
// > time:0 Promise {<penging>};
// time;1500 bが実行
// time:0 xが実行
// time:200 cが実行
// 実行順: c = b → x
// xはbの実行が終わってから実行された
a().then(b).then(c);
// > time:0 Promise {<penging>};
// time:1000 aが実行
// time:1500 bが実行
// time:1700 cが実行
// 実行順: a → b → c
a().then(c).then(b);
// > time:0 Promise {<penging>};
// time:1000 aが実行
// time:1500 bが実行
// time:200 cが実行
// 実行順: a → c = b
まとめるとこんな感じ。
実行した関数 | 実行順 |
---|---|
a().then(b).then(x) |
a → b → x |
b().then(c).then(x) |
b → c = x |
c().then(b).then(x) |
c = b → x |
a().then(b).then(c) |
a → b → c |
a().then(c).then(b) |
a → c = b |
c
の箇所には = が入っている。
つまりc.then(x)
としてもx
はc
の処理が終わる前に実行されてしまう。
ちなみにc
はasync
をつけていて何も返さない関数(なおasync
がついているため実際にはPromise<undefined>
が返ってくる)。
どうやら、内部にsetTimeout
を使っている場合、ただasync
を関数につけただけでは処理が終わるまで待つことはできない模様。
awaitをつけた場合
今度はawait
をつけて関数を実行してみます。
await a();
// time:1000 aが実行
// > time:0 undefined
await b();
// time:1500 bが実行
// > time:0 undefined
await c();
// > time:0 undefined
// time:1700 cが実行
await x();
// time:1700 xが実行
// > time:0 undefined
結果をまとめるとこんな感じ。
関数名 | 最後のログが出力されるまでの時間 | 値が返ってくるまでの時間 |
---|---|---|
a | 1000ミリ秒 | 1000ミリ秒 |
b | 1500ミリ秒 | 1500ミリ秒 |
c | 1700ミリ秒 | 0ミリ秒 |
x | 0ミリ秒 | 0ミリ秒 |
a
とb
が値が返ってくるまでに時間がかかるのに対し、c
はほぼ0秒で返ってくる。
やっぱりasync
をつけただけだと実行は待ってくれなそう。
まとめ
というわけで、実験結果をまとめていきたいと思います。
ここから敬語に戻ります。
前提として、関数の宣言の先頭にasync
をつけると、その関数はPromise
を返すようになります。
// asyncなし
function notAsync() {
return 'aaa';
}
notAsync(); // 'aaa'
// asyncあり
async function hasAsync() {
return 'bbb';
}
hasAsync(); // Promise {<fulfilled>: 'bbb'}
効果がない例
まず、Promise
を返す関数にawait
をつけた場合、関数はPromise
の結果を返すようになります。
Promise
を返さない関数にawait
をつけた場合、await
などなかったかのように関数は実行されます。
このとき、特にエラーなどは出ません。
// Promiseを返さない
function notAsync() {
return 'aaa';
}
await notAsync(); // 'aaa'、このawaitはないも同然
// Promiseを返す
async function hasAsync() {
return 'bbb';
}
await hasAsync(); // 'bbb'、このawaitはPromiseを見てくれるはず
例えば、setTimeout
はPromise
を返さないため、await setTimeout(() => {関数}, 1000)
のようにしても、その後に書いた処理はsetTimeout
内の処理が終わる前に実行されてしまいます。
async function test() { // awaitが入っている関数にはasyncが必要
await setTimeout(() => { // このawaitに効果はない
console.log('setTimeout内');
}, 1000);
console.log('setTimeout後'); // こっちが先に出力される
}
test();
// time:0 setTimeout後
// > time:0 Promise {<fulfilled>: undefined}
// time:1000 setTimeout内
そして、ただasync
をつけただけだとawait
しても効果はなく、関数の内部にawait
を含まないと処理の完了を待ってくれません。
例えば、setTimeout
を含む関数の先頭にasync
ををつけても効果はありません。
async function test() { // このasyncは効果なし
setTimeout(() => {
console.log('setTimeout内');
}, 1000);
}
await test(); // asyncは無いも同然状態のため、このawaitは効果なし
console.log('testの後'); // 先に出力される
// time:0 testの後
// > time:0 undefined
// time:1000 setTimeout内
効果がある例
じゃあ何なら効果あるのかと言いますと、要は内部でPromise
を返す関数なら何でもいいはずです。
例えば第一引数の時間だけ待つ以下の関数は、setTimeout
を含みますが、Promise
を返していてresolve
を呼び出しているので効果があります。
function sleep(sec) { // asyncはあってもなくても同じ
return new Promise((resolve, reject) => { // Promiseを返している
setTimeout(() => { resolve() }, sec); // secミリ秒後にresolve
});
}
await sleep(1000);
console.log('sleep後'); // 1000ミリ秒後に出力される
また、最初からPromise
を返してくれる関数に対しても使えます。
例えばfetch
などです。
const response = await fetch('url');
// urlからjsonを返す関数、特に使い道はない
async function getJson(url) {
const res = await fetch(url); // fetchはPromiseを返す
return res.json();
}
// 使うとしたらこんな感じ
const { sumple } = await getJson('url');
結論
Promise
は便利です。
これを使うと今までできなかったことができるようになります。
またasync/await
を使うとコードを大幅に短くすることができます。
そしてネストが浅くなります。
なのでこの2つは積極的に使っていきましょう。
ちなみにこの結論はうまいこと話をまとめるためだけに書かれました。
なので特に意味はありません。
以上です。