今回は、
- 非同期処理ってそもそもなに?
- どんなときに使えばいい?
- awaitの使い方は待っているの?
- なぜPromiseではなくてasync/awaitがいいの?
こんな悩みを解決するために
非同期処理(async/wait)とはなんぞやをわかりやすく紹介していきます。

非同期処理はasyncで書いて、awaitで受け取るんだよな(?)



先輩、非同期処理詳しくおしえてー



いいよー。ま、また今度時間作るね
(曖昧な理解しかしてないんだよな、
…教えるにはしっかり理解しないと)
僕も少し前までは、こんな状況でした。
今回人に教える機会があり、しっかり理解する必要があったため、
自分なりに学習し直してまとめました。
同じ悩みを持っている人の助けになればと思ったので、記事に残します。
非同期処理(async/await)とは
基本的に全ての命名には意味があることが多いので、由来から調べます。
asyncはAsynchronous(エイシンクロノス)=非同期の略語です。
awaitはasync wait(非同期を待つ)の略語です。
さて、由来はわかりましたが、そもそも「非同期」がイメージわかないですよね。
いつもの書いているプログラムを想像してください。
基本的には上から下に向かって処理が行われると思います。
上から1行ずつ順番に行われていくのが同期処理=順次処理です。
例えばプログラムが膨大な量のデータを読み込んでそのデータを処理して画面描画するとします。
同期処理の場合どの様な動きになるでしょうか。
答えは、「データの読み込みが完了するまで、画面には何も描画されず、データ処理が完了したら画面描画される」です。
非同期処理を使用すると、
画面の枠組みだけ先に表示させておいて、
データ部分は読み込みが終わったときに表示させることができます。
この投稿を例とするのであれば、
①ナビゲーションやフッターメニューなど軽い描画処理
②本文(文章や画像)などデータベースに取得しにいく重い描画処理
を分割して、後者の重い処理はバックグラウンドで行って、準備でき次第処理するよ。
というイメージで、まっさらなローディング画面をサイト訪問者に対し表示するのではなく、
一時的なレスポンスとしてヘッダーやフッターの表示だけを行い、
時差で本文を表示させていくことができます。
待機ストレス軽減や離脱防止に効果があったりします。
実際の開発現場などでは
- 外部通信
- データ取得
に非同期処理が使われていることが多いです。
asyncの役割
まずはこのコードの返り値を見てみましょう。
async function testAsync() {
return "here is sample";
}
const result = testAsync();
console.log(result);
返り値は
Promise { ‘here is sample’ }
とPromiseを返しています。
そうです。「パッと!ピッと!プロミス」です。
このPromise(約束)というのは非同期処理の操作が完了したときに返される値です。
今回のようにasync関数で直接returnすると、
値がPromise.resolve()でPromiseオブジェクトとしてカプセル化されます。
async関数の返り値はカプセル化されたPromiseオブジェクトとわかりました。
Promiseで書いたプログラムです。
const testPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("here is sample");
}, 300);
});
console.log(testPromise);
testPromise.then((value) => console.log(value));
Asyncの出力結果とPromiseの出力結果と比べてみると、特に違いが見受けられません。
awaitの役割
asyncは従来のpromiseとほぼ同等とわかったところで、awaitに注目していきます。
await
式はasync
関数の実行を一時停止し、Promise
が決定される(すなわち履行または拒否される)まで待ち、履行された後にasync
関数の実行を再開します。最下位時に、await
式の値は履行されたPromise
の値になります。
Promise
が拒否された場合、await
式は拒否された値で例外を発生させます。https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/await#%E8%A7%A3%E8%AA%AC
await
演算子に続く式の値がPromise
ではなかった場合、解決された Promise に変換されます。
公式は安定の非常に分かりづらい書き方。
asyncの役割で紹介したようにasyncが返すのはPromiseオブジェクト。
すなわち、ここではawaitでasync関数の結果を待てると理解できます。
ただし、awaitはasync関数の結果だけを待っているわけではありません。
引用文の最後の1行に「Promiseではなかった場合」と書かれてあるのです。
ようするに、awaitはawait演算子に続く式の結果を制限なく待っているのです。
なので、以下のような同期・非同期関数ともに受け取ることがきます。
function normalFunc() {
return "普通の関数です";
}
async function testAsync() {
return Promise.resolve("非同期関数です");
}
async function test() {
const v1 = await normalFunc();
const v2 = await testAsync();
console.log(v1, v2);
}
test();
awaitの動きが少し見えてきたところで
- awaitでPromiseオブジェクト以外の値を受け取った場合
- awaitでPromiseオブジェクトを受け取った場合
の動きの違いを見てみましょう。
Promiseオブジェクトではない場合は、awaitの演算結果=受け取った値となります。
簡単な代入式みたいなイメージですね。
Promiseオブジェクトの場合は、後続の処理を待機させ、Promiseがresolveされます。
awaitの演算結果=resolveされた値となります。
非同期処理を「promise/then」と「async/await」で書いて比較してみる
setTimeoutを使った2秒待つ非同期処理を例に書いていきます。
function waitTwoSecond() {
return new Promise(resolve => {
setTimeout(() => resolve("2秒待ちました"), 2000);
});
}
// Promise・then
waitTwoSecond().then(v => {
console.log(v);
});
// async・await
async function testAsyncFunc() {
const v = await waitTwoSecond();
console.log(v);
}
testAsyncFunc();
結果は同じですね。(厳密には違うオブジェクトです)
ここまで見てみると、async/awaitがこんなに利用されている意味がわからないですね。
そうなのです。Promise連鎖単体だとそのメリットは見えてきません。
ではこちらの処理を実行していく処理を書いてみます。
function waitFewSec(ms) {
return new Promise(resolve => {
setTimeout(() => resolve(ms + 2000), ms);
});
}
function stepOne(ms) {
console.log(`stepOne ${ms}ms待ちます`);
return waitFewSec(ms);
}
function stepTwo(ms) {
console.log(`stepTwo ${ms}ms待ちます`);
return waitFewSec(ms);
}
function stepThree(ms) {
console.log(`stepThree ${ms}ms待ちます`);
return waitFewSec(ms);
}
まずはPromise/Then
function doAllStep() {
console.time("promiseThenの処理時間");
stepOne(500)
.then(time => stepTwo(time))
.then(time2 => stepThree(time2))
.then(result => {
console.timeEnd("promiseThenの処理時間");
});
}
doAllStep();
続いてasync/await
async function doAllStep() {
console.time("asyncAwaitの処理時間");
const time = await stepOne(500);
const time2 = await stepTwo(time);
const result = await stepThree(time2);
console.timeEnd("asyncAwaitの処理時間");
}
doAllStep();
同じ処理をしていますが、async/awaitのほうがスッキリ簡潔にかけていますよね。
各stepの処理の引数が増えた場合を考えてみてください。
promise/thenの記述方法だとかなり恐ろしくなってきます。
さいごに
Javascriptのasync/awaitについて理解を深めてきました。
言葉で説明を受けて全てを理解するのは難しいと思います。
なので、記事を読み終わったら一度サンプルコードを動かして理解を更に深めることをおすすめします。
コメント