豆腐とコンソメ

豆腐とコンソメ

もろもろのプログラム勉強記録

コールバック地獄を体験したいんじゃ~Promiseへの道~(2)

日記

f:id:konoemario:20180114221306p:plain:w300

風邪が長引いてランニングも勉強もちょっとさぼりぎみ。

ぐうたら寝てばかり。

こういう時期ってあるよね。

前回の続き

前回、以下の処理をcallback関数を使って実現しました。

www.tohuandkonsome.site

  • ファイルを読み込む
  • 読み込んだファイルの内容のチェックをする
  • チェックが通れば、/sampleにGETリクエストを飛ばす
  • 上記のリクエストが終わったら、/otherにリクエストを飛ばす

実際、書いてみると、どの処理が実行されるのかがわかりにくい、という問題を実感することができました。

なので、Promiseを使って書き直したいと思います。


前回のファイル読み込み

前回は、こんな感じでファイルを読み込むReadFile関数を作成していました。
ファイル読み込みが終わった後の処理は、callback関数を渡すことで実現していました。

ReadFile.js(Promise使用前)

module.exports = function(file, callback){
    let reader = new FileReader();

    //読み込み終わったあとのイベント
    reader.onload = function(){
        text = reader.result

        //callbackを使う
        callback(text)
    }
    //読み込み開始
    reader.readAsText(file)
}

ReadFile.jsを実際に使っている処理はこんな感じでした。

app.js

        //ファイルを選択またはドロップ
        onDrop:function(event){
            //ファイルを取得
            let file = event.target.files[0];

            //ReadFile.jsの中でやってほしいことを書く
            const callback = function(text){
                //ファイルの中の「おはんき」の出現回数を数える
                let count = (text.match(new RegExp('おはんき','g')) || []).length

                //チェックがOKだったら
                if(count > 0){
                    //サーバーにリクエストを飛ばす
                    //リクエストが成功した後の処理を書く
                }
            }

            //定義した関数を渡す
            let text =  ReadFile(file, callback);
        },


Promiseを使ったファイル読み込み

さっそくPromiseを使ってみます。
といっても簡単で、Promiseオブジェクトを返すようにしてあげるだけです。
実際の処理は、Promiseオブジェクトの引数として書きます。

処理が正常に終わったときにresolve()を書いてあげます。
処理がエラーの場合はreject()を書くのですが、今回は面倒なので省略しちゃってます。

ReadFile.js(Promise使用)

module.exports = function(file){
    return new Promise(function(resolve, reject){
        let reader = new FileReader();

        //読み込み終わったあとのイベント
        reader.onload = function(){
            let text = reader.result
            resolve(text);
        }

        //読み込み開始
        reader.readAsText(file)

        })
}

上記の関数を実際に呼んでみます。

以下のように、ReadFileを呼んだ後に.then().catch()を使います。

app.js

        //ファイルを選択またはドロップ
        onDrop:function(event){
            //ファイルを取得
            let file = event.target.files[0];

            //ファイル読込
            ReadFile(file)
            .then((text) =>{
                console.log(text);
            })
            .catch((error) =>{
                console.log(error);
            });
        },

.then()ReadFile関数内でresolveした場合に実行される処理を書きます。
一方、.catchReafFile関数内でrejectした場合に呼ばれる処理になります。


ファイルのチェック

ファイルのチェックは、以下のように.then()に続けて書くこともできます。
ですが、ここではファイルのチェック処理もPromiseを使うことで、さらに見通しをよくしようと思います。

app.js

        //ファイルを選択またはドロップ
        onDrop:function(event){
            //ファイルを取得
            let file = event.target.files[0];

            //ファイル読込
            ReadFile(file)
            .then((text) =>{
                 //ここにtextの内容をチェックする処理を書く。  
            })
            .catch((error) =>{
                console.log(error);
            });
        },

まず、ファイルのチェック処理もファイル読み込みと同様の形でモジュール化しておきます。

Validate.js

module.exports = function(text){
    return new Promise(function(resolve, reject){
        let count = (text.match(new RegExp('おはんき','g')) || []).length

        //チェックがOKだったら
        if(count > 0){
            resolve();
        }else{
            reject('ファイルの内容がおかしいよ');
        }

    })
}

そうしたら、以下のようにReafFile関数の.then()内で呼ぶことにします。

app.js

        //ファイルを選択またはドロップ
        onDrop:function(event){
            //ファイルを取得
            let file = event.target.files[0];

            //ファイル読込
            ReadFile(file)
            .then((text) =>{
                 //チェック処理
                    return Validate(text);
            })
            .then(() =>{
                //チェック処理が終わったあとの処理を書く
            })
            .catch((error) =>{
                console.log(error);
            });
        },

ポイントは、Validateではなく return Validateするところになります。
Promiseオブジェクトをreturnしないとその後のthen()が順番通りに動いてくれません。


サーバーにリクエストを投げる

ここまでくると後は作業になります。 前回作ったXhr.jsもPromiseを使います。

こんな感じにしました。

Xhr.js

module.exports = function(url){
    return new Promise(function(resolve, reject){
        var xhr= new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.send(); 

        //リクエストを受信したときのイベント
        xhr.onload = function(){
            if(xhr.readyState === 4 && xhr.status === 200) {
                resolve(xhr.responseText);
            }else{
                reject('リクエストに失敗しているよ');
            }
        };
    })
}

使う際も以下のような形になります。

app.js

        //ファイルを選択またはドロップ
        onDrop:function(event){
            //ファイルを取得
            let file = event.target.files[0];

            //ファイル読み込み
            ReadFile(file)
            .then((text) =>{
                //ファイルチェック
                return Validate(text);
            })
            .then(() =>{
                //サーバーリクエスト
                return Xhr('/sample');
            })
            .then((text) =>{
                //サーバーリクエスト
                console.log(text);
                return Xhr('/other');
            })
            .then((text) =>{
                console.log(text);
            })
            .catch((error) =>{
                console.log(error);
            });

        },

これで、上から下に処理が流れるということが前回よりもだいぶはっきりしました!


サーバーリクエストにaxiosを使う

axiosはPromiseオブジェクトを返す便利なAjaxリクエストができるモジュールになります。
使い方も、今まで使っていたXhr.jsを置き換える以下のように置き換えるだけで使うことができます。

app.js

        onDrop:function(event){
            //ファイルを取得
            let file = event.target.files[0];

            //ファイル読み込み
            ReadFile(file)
            .then((text) =>{
                //ファイルチェック
                return Validate(text);
            })
            .then(() =>{
                //サーバーリクエスト
                return axios.get('/sample');
            })
            .then((text) =>{
                //サーバーリクエスト
                console.log(text);
                return axios.get('/other');
            })
            .then((text) =>{
                console.log(text);
            })
            .catch((error) =>{
                console.log(error);
            });

        },

だいぶなげやりな感じになってしまいましたが、なんとなくPromiseを使うことができるようになりました!

この調子でasync/awaitも使えるようになりたいですね!