3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptAdvent Calendar 2023

Day 8

【JavaScript】async/awaitについて理解を深めたい

Posted at

やりたいこと

async/awaitPromiseが全然理解できていないと思ったため、よくわからないと思ったところを実験してみることにしました。
この記事はその実験結果をまとめたものです。

この実験をしたのが記事の執筆時から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:数値は前のログが出力されてから経過した時間を示しています。

特に何も考えず実験結果を常体で書いてしまったため、
ここから敬語じゃ無くなります。

何もつけなかった場合

aasyncがあってPromiseを返す関数。

aを実行
a();
// > time:0 Promise {<penging>}
// time:1000 aが実行

basyncが無くてPromiseを返す関数。

bを実行
b();
// > time:0 Promise {<penging>}
// time:1500 bが実行

casyncがあってPromiseは返さない関数。

cを実行
c();
// > time:0 Promise {<fulfilled>: undefined}
// time:1700 cが実行

xasyncが無くてPromiseも返さない関数。

xを実行
x();
// time:0 xが実行
// > time:0 undefined

結果をまとめるとこんな感じ。

関数名 返り値 値が返ってくるまでの時間
a Promise {<penging>} 0ミリ秒
b Promise {<penging>} 0ミリ秒
c Promise {<fulfilled>: undefined} 0ミリ秒
x undefined 0ミリ秒

thenawaitなどをつけないで実行した場合、値が返ってくるまでの時間(=処理が終了するまでの時間)はほぼ0。

また、abは全く同じ結果だった。
cabと返り値が違った。

thenを使った場合

次はa, b, cの関数にthenをつけて実行してみる。
thenで実行するのはx

aasyncがあってPromiseを返す関数。

aを実行
a().then(x);
// > time:0 Promise {<penging>};
// time:1000 aが実行
// time:0 xが実行

basyncが無くてPromiseを返す関数。

bを実行
b().then(x);
// > time:0 Promise {<penging>};
// time:1500 bが実行
// time:0 xが実行

casyncがあってPromiseは返さない関数。

cを実行
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が実行された。

また、前と同じくabの実験結果は変わらなかった。
ログの出力順的にcだけ実行完了を待っていないように思える。

次は複数の関数をthenで実行してみる。

aとbを実行
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とcを実行
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とbの実行順を逆にする
c().then(b).then(x);
// > time:0 Promise {<penging>};
// time;1500 bが実行
// time:0 xが実行
// time:200 cが実行

// 実行順: c = b → x
// xはbの実行が終わってから実行された
aとbとcを実行
a().then(b).then(c);
// > time:0 Promise {<penging>};
// time:1000 aが実行
// time:1500 bが実行
// time:1700 cが実行

// 実行順: a → b → c
aとcとbを実行
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)としてもxcの処理が終わる前に実行されてしまう。
ちなみにcasyncをつけていて何も返さない関数(なおasyncがついているため実際にはPromise<undefined>が返ってくる)。

どうやら、内部にsetTimeoutを使っている場合、ただasyncを関数につけただけでは処理が終わるまで待つことはできない模様。

awaitをつけた場合

今度はawaitをつけて関数を実行してみます。

aを実行
await a();
// time:1000 aが実行
// > time:0 undefined
bを実行
await b();
// time:1500 bが実行
// > time:0 undefined
cを実行
await c();
// > time:0 undefined
// time:1700 cが実行
xを実行
await x();
// time:1700 xが実行
// > time:0 undefined

結果をまとめるとこんな感じ。

関数名 最後のログが出力されるまでの時間 値が返ってくるまでの時間
a 1000ミリ秒 1000ミリ秒
b 1500ミリ秒 1500ミリ秒
c 1700ミリ秒 0ミリ秒
x 0ミリ秒 0ミリ秒

abが値が返ってくるまでに時間がかかるのに対し、cはほぼ0秒で返ってくる。
やっぱりasyncをつけただけだと実行は待ってくれなそう。

まとめ

というわけで、実験結果をまとめていきたいと思います。

ここから敬語に戻ります。

前提として、関数の宣言の先頭にasyncをつけると、その関数はPromiseを返すようになります。

先頭に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などなかったかのように関数は実行されます。
このとき、特にエラーなどは出ません。

awaitをつける
// Promiseを返さない
function notAsync() {
    return 'aaa';
}
await notAsync(); // 'aaa'、このawaitはないも同然

// Promiseを返す
async function hasAsync() {
    return 'bbb';
}
await hasAsync(); // 'bbb'、このawaitはPromiseを見てくれるはず

例えば、setTimeoutPromiseを返さないため、await setTimeout(() => {関数}, 1000)のようにしても、その後に書いた処理はsetTimeout内の処理が終わる前に実行されてしまいます。

setTimeoutにawaitしても効果なし
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ををつけても効果はありません。

awaitのない関数に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などです。

awaitに効果がある例
const response = await fetch('url');
asyncとawaitに効果がある例
// 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つは積極的に使っていきましょう。

ちなみにこの結論はうまいこと話をまとめるためだけに書かれました。
なので特に意味はありません。

以上です。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?