JavaScriptのPromiseとasync/awaitについて、自分用のメモとしてまとめてみました!
参考になれば幸いです。
目次
【解説】JavaScriptのPromise使い方
JavaScriptのPromiseを説明するにあたって、下記の順に解説していきます。
①:大前提
②:コールバック地獄
③:そこで使えるのがPromise
④:Promiseが持つプロパティ
⑤:Promiseの引数に値が入る
⑥:PromiseStateの状態をまとめると
⑦:Promiseが持つメソッド
⑧:Promiseメソッドチェーン
⑨:これで②が解決!
順に見ていきましょう。
JavaScriptは非同期言語です。
前の処理に時間がかかると、その実行完了を待たずにに次の処理が実行されます。
つまり下記の場合、出力結果は上から順というわけにはいかず「1回目⇒3回目⇒2回目」となってしまうんですよね。
console.log('1回目');
setTimeout(() => {
console.log('2回目(1秒後)');
}, 1000)
console.log('3回目');
1回目
3回目
2回目(1秒後)
では先述したものをちゃんと上から順に実行してほしい場合どうすればよいのでしょう。
「setTimeoutをネストすればよいのでは?」という考えも浮かぶかと。
ただそうなると下記のようになり、ネストすることで可読性が下がり、修正や拡張が面倒になります。
console.log('1回目');
setTimeout(() => {
console.log('2回目');
setTimeout(() => {
console.log('3回目');
setTimeout(() => {
console.log('4回目');
}, 1000)
}, 1000);
}, 1000)
これを「コールバック地獄」と呼んだりします。
コールバック地獄を解決しつつ、処理の順番通りに実行したいときに役立つのが「Promise」です。
処理の実行が終わるまで待機したり、処理結果に合わせて次の処理をしてくれるのが特徴です。
翻訳すると「約束」だね。
処理を待ってくれるのを約束してくれる!
「Promise」の基本的な書き方がこちら。
const 変数名 = new Promise(() => {
});
第一引数にコールバック関数を入れる必要があります。
ちなみにインスタンス化された瞬間に、その関数が実行されます。
Promiseは内部的にプロパティを持っています。
それが「PromiseState」と「PromiseResult」です。
下記のようにコンソールログで見てみると、プロパティが見れました。
const testPromise = new Promise(() => {});
console.log(testPromise);
これがそのプロパティと初期値になります。
それぞれの意味合いを簡単に書くと下記になります。
プロパティ | 値 |
---|---|
PromiseState | ・pending(待機、初期状態) ・fulfilled(処理成功) ・rejected(処理失敗) |
PromiseResult | 結果を保持(初期値はundefined) |
先述した「PromiseState」「PromiseResult」の値は、変更することができます。
そのやり方はコールバック関数の引数を使うことです。
実はコールバック関数が実行されるときに、Promiseが第1・第2引数に関数オブジェクトの値を入れてくれるんですよね。
それを活用します!
引数の名前は任意ですが、一般的に「resolve」「reject」と書いて使うことが多いです。
const testPromise = new Promise((resolve, reject) => {});
例として「resolve()」を使ってみました。
const testPromise = new Promise((resolve, reject) => {
resolve('成功した!');
});
console.log(testPromise);
すると「PromiseState」が「fulfilled」になり、「PromiseResult」にresolveの引数に入れた値が入りました。
例として「reject()」を使ってみました。
const testPromise = new Promise((resolve, reject) => {
reject('失敗した!');
});
console.log(testPromise);
すると「PromiseState」が「rejected」になり、「PromiseResult」にrejectの引数に入れた値が入りました。
PromiseStateの状態をまとめると下記になります。
pending(初期値)
↓
resolved()を使うと
↓
fulfilled(処理成功)
pending(初期値)
↓
reject()を使うと
↓
reject(処理失敗)
ちなみに一度resolve()やreject()が行われて値が変わると、もうその値は変わりません。
resolve()をした後にreject()を行ったりしても、値が変わらないのが特徴です。
Promiseは下記のメソッドを持っています。
「PromiseState」の値によって、実行されるかどうかが変わります。
メソッド | 実行条件 |
---|---|
then() | 「PromiseState」が「fulfilled」のときに実行 |
catch() | 「PromiseState」が「reject」のときに実行 |
finally() | 「PromiseState」が「fulfilled」のときに実行 「PromiseState」が「reject」のときに実行 |
下記が「PromiseState」が「fulfilled」のときに実行される処理です。
const testPromise = new Promise((resolve, reject) => {
resolve('成功した!');
})
testPromise.then((value) => {
console.log('thenメソッド'); //thenメソッド
console.log(value); //成功した!
})
testPromise.finally(() => {
console.log('finallyメソッド'); //finallyメソッド
});
ちなみにthenの第一引数には、PromiseResultの値が入ります。
そして下記が「PromiseState」が「reject」のときに実行です。
const testPromise = new Promise((resolve, reject) => {
reject('失敗した!');
})
testPromise.catch((error) => {
console.log('catchメソッド'); //catchメソッド
console.log(error); //失敗した!
})
testPromise.finally(() => {
console.log('finallyメソッド'); //finallyメソッド
});
ちなみにcatchの第一引数には、PromiseResultの値が入ります。
実はPromiseのthenやcatchやfinallyの後ろには、いくつでもつなげることができます。
これがPromiseのメソッドチェーンです。
const testPromise = new Promise((resolve, reject) => {
resolve('成功した!');
})
.then(() => {
console.log('1'); //1
})
.then(() => {
console.log('2'); //2
})
.then(() => {
console.log('3'); //3
})
ここまで学んだPromiseを活用すれば、②で解説したコールバック地獄を解決することができます。
そんなコールバックがこちらでした↓
console.log('1回目');
setTimeout(() => {
console.log('2回目');
setTimeout(() => {
console.log('3回目');
setTimeout(() => {
console.log('4回目');
}, 1000)
}, 1000);
}, 1000)
それをPromiseを活用すれば。。。
↓
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
delay(1000)
.then(() => {
console.log('2回目');
return delay(1000);
})
.then(() => {
console.log('3回目');
return delay(1000);
})
.then(() => {
console.log('4回目');
});
コールバックのときよりかは見やすくなりましたよね。
ただ.thenでつなぐことで少し冗長ではあります。。
そこで活躍するのが次に紹介する「async/await」なんですよね。
【解説】JavaScriptのasync/awaitの使い方
async/awaitを使えば、Promiseよりも見やすくコードを書くことが可能です。
使い方として下記の順に解説していきます。
①:asyncとは
②:awaitとは
③:Promiseとコードを比較
asyncとは関数が非同期であることを宣言するようなキーワード。
書き方は関数の前につけます。
async function 関数名() {};
こうすることで、下記ができるようになります。
- その関数はPromiseを返す
- その関数の中でawaitが使用できる
- returnするとPromiseがその戻り値をresolveする
- returnすると戻り値がPromiseResultになる
実際に試したコードがこちら↓
async function testFun() {
return 'テストです';
};
console.log(testFun());
awaitとはPromiseが値を返すまで実行を待ち、その値を返す演算子のこと。
- Promise が解決されるまで処理を一時停止
- Promise が解決された値を返す
- async関数の中で使う
実際に試したコードがこちら↓
async function testFun() {
const awaitTest = await new Promise((resolve) => {
setTimeout(() => {
console.log('テスト');
resolve('成功した!');
},1000)
})
console.log(awaitTest)
};
testFun();
テスト
成功した!
awaitTest には、resolve の引数 「成功した!’」が代入されます。
そしてawatiにより待ってくれたため、その後にその下のコンソールログが出力されます。
最後にPromiseのコードと、async/awaitで書いたコードを見比べてみましょう。
まずPromiseがこちら。
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
delay(1000)
.then(() => {
console.log('2回目');
return delay(1000);
})
.then(() => {
console.log('3回目');
return delay(1000);
})
.then(() => {
console.log('4回目');
});
そしてasync/awaitで書いたコードがこちら↓
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function testFun() {
console.log("1回目");
await delay(1000);
console.log("2回目");
await delay(1000);
console.log("3回目");
await delay(1000);
console.log("4回目");
}
testFun();
多少見やすくなりましたよね!
【解説】JavaScriptのPromise|async/awaitの使い方:まとめ
- Promiseを使うことで処理の実行が終わるまで待機してくれる
- 第一引数にコールバック関数を入れる必要あり
- 「PromiseState」と「PromiseResult」を内部的にもつ
- 第一・第二引数に関数オブジェクトの値を入れてくれる
- メソッドを持っており「PromiseState」の値により発動条件がある
- thenやcatchやfinallyの後ろには、いくつでもつなげることが可能
- async/awaitを使えばさらに簡潔にコードを書ける
Promiseやasync/awaitを使う時はためしてみてね!