前時代の産物、[[コールバック関数 (JavaScript)|コールバック関数]]について前回学びました。今回から現代における非同期処理の中核を担う[[Promise (JavaScript)|Promise]]について理解を深めていきます。 ## Reference - [非同期処理:Promise/Async Function · JavaScript Primer \#jsprimer](https://jsprimer.net/basic/async/) ## Lesson ### Promiseの概念 [[Promise (JavaScript)|Promise]]は **非同期処理の結果** を表現したオブジェクトです。イメージとしては、`カップラーメンにお湯を入れる()` の返却値が `Promise<食べられるラーメン>` みたいな感じです。今すぐには値を取り出せない(確定しない)けど、しばらくして処理が終わったら取り出せるモノです。 ### コード例 実際のコード例を見てみましょう。おそらく、初見では何をやっているのか意味が分からないと思われるかもしれません。 ```js function createCapRamenPromise() { // Promiseを返却する return new Promise((resolve) => { // 非同期処理を書く setTimeout(() => { // 非同期処理が終わったあと // Promiseが処理が正常終了した場合に返却したいデータをresolve()に入れる resolve("美味しいラーメン"); }, 3 * 1000); }); } console.log("Before createCapRamenPromise"); createCapRamenPromise() .then((result) => { // result には createCapRamenPromise の中で resolve(...) に指定した値が渡ってくる console.log(result); }); console.log("After createCapRamenPromise"); ``` 上記コードを実行すると、出力結果は以下のようになります。 ```console Before createCapRamenPromise After createCapRamenPromise 美味しいラーメン ``` ### コールバック関数と何が違うのか? 勘のいい方は気づくかもしれません。『[[Promise (JavaScript)|Promise]]より[[コールバック関数 (JavaScript)|コールバック関数]]の方がシンプルに書けるんでは...?』 と。 ```js /** * @param {(created: string) => {}} onCapRamenCreated */ function createCapRamenWithCallback(onCapRamenCreated) { setTimeout(() => { onCapRamenCreated("美味しいラーメン"); }, 3 * 1000); } console.log("Before createCapRamenWithCallback"); createCapRamenWithCallback((result) => { console.log(result); }); console.log("After createCapRamenWithCallback"); ``` 今回のコードに限って言えばそうかもしれません。しかし、[[Promise (JavaScript)|Promise]]という共通で使えるIFが定義されたことにより、変更に強い堅牢な非同期処理を書くことができるようになります。そして、よりシンプルな構文にも対応できるようになります。 今は深く考えずに、まずは[[Promise (JavaScript)|Promise]]を使った正常系のコードが書けるようになりましょう。 ## Mission 1 #😁EASY 以下のコードが期待通り動くように修正してください。 ```js // FIXME: 右辺を修正する const promise = new Promise(setTimeout(() => { return "3秒経った"; }, 3 * 1000)); console.log("Before"); promise.then((result) => console.log(result)); console.log("After"); ``` %% 解答例 ```js const promise = new Promise((resolve) => { setTimeout(() => { resolve("3秒経った"); }, 3 * 1000); }); console.log("Before"); promise.then((result) => console.log(result)); console.log("After"); ``` %% ## Mission 2 #🙂NORMAL 下記コードにおける、コメント A ~ H を実行される順に並び替えてください。 ```js function createCapRamenPromise() { // A return new Promise((resolve) => { // B setTimeout(() => { // C resolve("美味しいラーメン"); }, 3 * 1000); }); } // D console.log("Before createCapRamenPromise"); // E createCapRamenPromise() .then((result) => { // F console.log(result); }); // G console.log("After createCapRamenPromise"); ``` `解答例` ``` A -> B -> C -> D -> F -> G -> E ``` %% 解答例 ``` D -> E -> A -> B -> G -> C -> F ``` %% > [!hint]- Hint 1 > `new Promise(...)` に指定した関数は、[[Promise (JavaScript)|Promise]]のコンストラクタ内で実行されます。 ## Mission 3 #🙂NORMAL 以下のコードを指示に従い[[Promise (JavaScript)|Promise]]を使って書き直してください。 ```js // FIXME: 引数は変更せず、関数名と実装を変更する function delayConsoleLog(delaySec, output) { setTimeout(() => { console.log(output); }, delaySec * 1000); } // FIXME: 必要に応じて変更する delayConsoleLog(3, "hogehoge") ``` %% 解答例 ```js function delayOutput(delaySec, output) { return new Promise((resolve) => { setTimeout(() => { resolve(output); }, delaySec * 1000); }); } delayOutput(3, "hogehoge").then((r) => { console.log(r); }); ``` %% ## Mission 4 #😵HARD 以下のコードを指示に従い[[Promise (JavaScript)|Promise]]を使って書き直してください。 ```js // FIXME: 引数をなしにする function randomDelay(callback) { setTimeout(callback, Math.random() * 3000); } // FIXME: Promiseベースでいい感じにする randomDelay(() => { console.log("1"); randomDelay(() => { console.log("2"); randomDelay(() => { console.log("3"); }); }); }); ``` %% 解答例 ```js function randomDelay() { return new Promise((resolve) => { setTimeout(resolve, Math.random() * 3000); }); } randomDelay() .then(() => { console.log("1"); return randomDelay(); }) .then(() => { console.log("2"); return randomDelay(); }).then(() => { console.log("3"); }); ``` %% > [!hint]- Hint 1 > [[Promise.prototype.then (JavaScript)|Promise.prototype.then]] に指定した関数の中で[[Promise (JavaScript)|Promise]]を返すと、ネストせずに `then` を繋げる形で直列処理を表現できます。 [Promiseチェーンで逐次処理](https://jsprimer.net/basic/async/#promise-sequential) も参考にしてください。 --- *NEXT* >> [[📗TDQ-025 Promiseの基本 異常系]]