noteのタイトル画像

Promise(約束)の値(地) ・・・え?音楽?

目次
・Promise 約束の地・・・え?初音ミク?
・非同期処理にかかせないPromise
・コールバック地獄って?
・「Promise」は便利だけど (金融機関とは関係ありません)
・おそらく一番の教則本「Promiseの本」

●Promise 約束の地・・・え?初音ミク?

インターネットを使う上で欠かせない検索エンジン。
しかし時々トンデモないモノをヒットさせてくれる。ま、ご愛嬌ってやつでしょうね。

第二次大戦中の軍艦を検索していたら何故か「萌えキャラ」がヒットするようになった理由に驚愕したり、毎日一つくらいは検索エンジンの「おちゃめな検索候補」に苦笑いする日々です。

今回の”変な検索結果大賞”は「Promiseの値」と検索しようとして「値」が「地」と出てしまい「Promiseの地」を検索した時のことです。

 promiseの地 → Promise 約束の地

え?初音ミク?

に飛ばされてしまい、就業時間中に変なサイトを閲覧している奴として通報されそうになりました。

私が検索したかったのは JavaScriptの「Promise」オブジェクトに関する情報です。

●非同期処理にかかせないPromise

今回取り上げるPromiseは、音楽でもないし、黄色い看板でもありません。

Promiseは

JavaScriptの非同期処理を長年苦しめてきた「コールバック地獄」から解放してくれる救世主

です。

そもそも「コールバック地獄」ってなに?って思われる方も居るでしょう。
javascriptを書き始めのころ、このコールバックを多段構成にして、後で誰が読んでも非常に読みにくいソースコードにしてしまった経験はjavascript屋さんなら一度や二度はあるはず。

●コールバック地獄って?

次のようなプログラムを考えてみましょう。

機能A(funcA)は外部リソースからデータを取得する関数だとします。
この機能Aは処理にとても時間がかかるとします。

javascript・node.jsのプログラムはシングルスレッド(処理を担当する人が一人だと考えてください)で動作しますので、一つの処理に長い時間をかけて他の処理がロック(待ち)されるのを嫌います。

そこで頭の良い人が考えました。

「時間がかかる処理は、処理が終わったら教えてくれるように誰かに頼んでおいて、私は別な仕事を先に進めよう」

と。

処理がおわった時に「ここを呼び出して」とお願いする関数を「コールバック関数」と言います。

function funcA(callback) { ・・・ }

例として、上記のfuncAは引数に「コールバック関数(callback)」を取るように設定されています。

プログラムからこの「funcA」を呼び出すと、funcAはすぐに戻りを返し、処理が先のステップに進みます。

「あれ?もうfuncAの処理が終わった?」

と思うことでしょうが、時間のかかる処理の場合は処理はまだ実行中です。

本当に処理が終わった時に、funcAの引数で設定した「コールバック関数」が呼び出されて、処理が終わったことを通知する仕組みになっているのです。

うわー、便利♫

ですが、すべての処理を非同期で進めて良い場合ばかりとは限りません。
時間のかかる処理を後に残してどんどんと先に進めていくと不都合も多々あります。

そんなときに「処理が終わるまで待って」から次の処理を進めるにはどうすれば良いか、が問題になります。

上記の例ではコールバック関数の中に「funcAの処理が終わった後に実行させたい処理」を書けば良いのです。

function funcA( function cb() {
             ・・・funcAの後で行う処理・・・
            }) { ・・・ }

さて、これで一件落着♫と言いたいところですが、これが何段にも組み合わさるとどうなるでしょう。
コールバック関数の中の処理でも別の非同期処理を呼び出す必要があった場合です。

例えばfuncBも非同期処理であり、コールバック関数が必要でした。

function funcA( function cb() {
             ・・・funcAの後で行う処理・・・
            funcB( function cb() {
                       ・・・funcBの処理の後で行う処理・・・
                   });
            }) { ・・・ }

さあ、大変です。このまま何段にも繋がったら

function funcA( function cb() {
             ・・・funcAの後で行う処理・・・
            funcB( function cb() {
                       ・・・funcBの処理の後で行う処理・・・
               funcC( function cb() {
                           ・・・funcCの処理の後で行う処理・・・
                    funcD( function cb() {
                                 ・・・funcDの処理の後で行う処理・・・
                             });
                        });
                   });
          }) { ・・・ }

もう何が何だかわかりませんね。

これが「コールバック地獄」です。

実際問題、そこまで多段構成になることはあまりないかもしれませんが、ファイル読み込みをストリームで扱って、それをネットワークを介して配信したりする処理を書くと、、、意外とハマります。

●「Promise」は便利だけど (金融機関とは関係ありません)

そこでPromise(約束)の登場です。

私も自分のプログラムの中で作ったり、使ったりしています。

Promiseとは

非同期処理を抽象化したオブジェクトとそれを操作する仕組み

のことだそうです。

このPromiseの導入によって、非同期処理の記述の方法が統一されたインターフェイスで強制されるので、先ほどのようなコールバック地獄に陥らなくて済むようになります。

一番簡単なPromiseの例は「sleep関数」でしょう。

大抵どこのPromise紹介サイトでも掲載されているポピュラーなものです。

const sleep = async (time) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    })
}

node.jsは非同期処理というかノンブロッキングI/Oが基本なので、処理に待ちを加えるsleep関数が実装されていません。

なので、たかがsleep関数とはいえ自作が必要です。
上記の関数はsetTimeout関数を使って指定時間後にresolveコールバックを呼び出しています。

Promiseオブジェクトでは、必ず最後には
・成功:resolve
・失敗:reject
を呼び出すと仕様上決まっています。

それ以外の終わり方はありません。(ペンディングという状態は取らない)

そしてresolveが呼び出された時にそれを受け止めるには
 then()
を、rejectが呼び出された時にはそれを受け止めるには
 catch()
を使います。

const promise = new Promise((resolve, reject) => {
        // 何らかの処理
        // 成功時 resolve
        // 失敗時 reject
    }

promise.then({
            // ①成功時
        })
        .catch({
            // ②失敗時
        });

もし上記の①で、さらにpromiseを戻せば、thenを繋げていって処理を次々に伝搬させることが可能です。

promise.then({
            // ①成功時
            return 別のPromiseオブジェクト
        })
        .then({
            // 処理
            return 別のPromiseオブジェクト
        })
        .then({
            // 処理
            return 別のPromiseオブジェクト
        })
        .then({
            // 処理
            return 別のPromiseオブジェクト
        })
        .catch({
            // ②失敗時
        });

先ほどのコールバック地獄から”多少は”改善されて感がありますね。

便利便利と思って「ガンガン」使うとやっぱり「then-catch地獄」になってしまいますけどね。

●おそらく一番の教則本「Promiseの本」

私がこれからエッセンスをご紹介しても良いのですが、おそらく次にご紹介するサイト以上のものは書けないのではないかと思うくらい良くできたサイトがあります。

サンプルも豊富だし、とにかく「親切丁寧」です。

内容的にasync/awaitが登場してtypescriptなんかが流行る前の情報がメインですが、「Promiseとは」をこれ以上無く詳しく解説しています。

私も迷ったらここでもう一度おさらいをしている良サイトです。

Promise.raceを使用した「タイムアウト検出」処理なんかは役に立つのではないでしょうか。

私が作成している

にも、まだ何箇所か拡張したいところが残っているので、今後は

・取引所APIの応答遅れや無応答時のタイムアウト処理

に活用していきたいと思っています。

ご期待いただければ「スキ」ボタンをポチっと、「フォロー」ボタンをクリック、よろしくお願いいたします。

note: https://note.mu/o_matsuo

twitter: @o_matsuo
もフォローしてくださると、喜びます。

あ、それから私の師匠である
コンドウ様のnoteもポチっとしていただけると、さらに喜びます。

ソフトウェア・エンジニアを40年以上やってます。 「Botを作りたいけど敷居が高い」と思われている方にも「わかる」「できる」を感じてもらえるように頑張ります。 よろしくお願い致します。