JavascriptでのPromiseの操作

6 min read読了の目安(約6100字

JavascriptにおいてPromiseは非常に興味深いトピックで、少し複雑なトピックでもあります。PromiseはJavascriptにおいてよく利用されます。

JavascriptのPromisesとは何か?

Promiseは、非同期操作の操作が完了したとき,または失敗したときにその結果の値を返すオブジェクトです。つまり、Promiseは特定の時間間隔の後に何らかの値を返します。値は、resolveまたはrejectのいずれかです。

以下のコードを考えてみましょう

    let promiseObj = new Promise(()=> 'some promise');

    console.log(promiseObj);

ある時点で、Promiseは以下の3つの状態のいずれかになります。

  1. pending -Promiseが作成されたときの初期状態。
  2. fulfilled -Promiseが正常に実行されたときの状態。
  3. rejected -何らかのエラーまたは何らかの条件が原因でPromiseが失敗したときの状態。
    const promise = new Promise((resolve, reject) => { 
        try{
            setTimeout(() => { 
                resolve('resolved promise'); 
            }, 5000); 
        }
        catch (e) {
            reject(e)
        }
    }); 

    // 結果 - 'resolved promise'
    promise.then((val) => console.log(val))

上記のコードのsetTimeout関数は、5秒後に実行されます。したがって、promiseは5秒後にresolveされると言えます。その後、その promise.then((val) => console.log(val))ブロックが実行され、resolved promiseの出力が返されます。

Promiseオブジェクトは、パラメータとしてexecutor関数を持ちます。

    new Promise(executor_function)

executor関数は、resolverejectかの2つのパラメータがあります。promiseが成功したときはresolveを返し、executorメソッドの最初のパラメーターであるメソッドでそれを実行します。失敗の値を持つpromiseをresolveする場合は、executorメソッドの2番目のパラメーターであるrejectメソッドを使用します。

    new Promise((resolve , reject)=>{
        try{
            resolve(/** ここで成功したときの処理 */)

        }catch (err){
            /** エラーが起こったときrejectする */
            reject(err)

        }
    })

Promiseメゾット

  1. Promise.all()

    Promise.all()メソッドは配列をパラメーターとして受け入れます。Promise.all()で渡された配列内のすべてのPromisesresolveまたはrejectされるのを待って、resolveされた新しい配列を返します。

        const promise1 = Promise.resolve(32);
        const promise2 = 1234;
        const promise3 = new Promise((resolve, reject)=>{
            setTimeout(()=> 'resolve something',2000)
        })
    
        Promise.all([promise1, promise2, promise3 ]).then(
            /** 
            * 2秒後に表示される
            * [32, 1234, 'resolve something']
            */
            val => console.log(val)
        )
    
    

    Promise.allの問題は、1つのPromiseが失敗したり、拒否されたりした場合、Promise.all全体が失敗して、それぞれのエラーが発生することです。そのため、Promise.allを実行する際には注意が必要です。Promise.allの利点は、並行してタスクを実行できることです。しかし、配列内のどのPromiseも失敗しないようにする必要があります。

  2. Promise.allSettled()

    Promise.allSettledは、パラメータとして配列を受け取り、すべてのPromiseresolveするのを待ちます。実行されたPromiseは、rejectされた値かresolveされた値のどちらかになります。Promiseが解決すると、resolveされたのPromiseの配列を返します。解決済みのPromiseの場合、return オブジェクトは、status フィールドを fulfill として、value フィールドをそのPromiseresolveされた値として持ちます。rejectされたPromiseに対しては、return オブジェクトはrejectedとしてステータスフィールドを持ちvalueフィールドの代わりに、Promiserejectされた理由を持つreasonフィールドを持ちます。

    
        const promise1 = Promise.resolve('resolved value');
        const promise2 = 23;
        const promise3 = new Promise(function(resolve, reject) {
            setTimeout(reject, 100, 'i will be rejected');
        });
    
        Promise.allSettled([promise1, promise2, promise3]).then(function(values) {
            console.log(values);
        });
    
    /**
    *
    * 上記のコードの結果
    *
    * Array [   Object { status: "fulfilled", value: 'resolved value' }, 
    *           Object { status: "fulfilled", value: 23 }, 
    *           Object { status: "rejected", reason: "i will be rejected" }
    *       ]
    *
    */
    
    
  3. Promise.race()

    Promise.raceは、パラメータとして配列を受け取り、最初にPromiseされたPromiseの値を返します。resolveしたPromiseは、resolveされるかrejectされるかのどちらかになります。rejectされた場合は、rejectされた最初のPromiseの理由を付けてrejectされます。

    
        const promiseMeApple = new Promise(function(resolve, reject) {
            setTimeout(resolve, 100, `🍎`);
        });
    
        const promiseMeGrapes = new Promise(function(resolve, reject) {
            setTimeout(resolve, 300, `🍇`);
        });
    
        Promise.race([promiseMeApple, promiseMeGrapes]).then(function(value) {
            console.log(value);
        });
    
  4. Promise.resolve()

    Promise.resolveは、新しいPromiseが返す値をパラメータとして受け取り、resolveした値で新しいPromiseを返します。

    
        let melonPromise = Promise.resolve(`🍉`);
    
        /** console log : [object Promise] */
        console.log(melonPromise);
    
        /**
        * 
        * 🍉 が表示される
        */
        melonPromise.then(val => console.log(val));   
    
    
  5. Promise.reject()

    Promise.reject は拒否される理由をパラメータとして受け取り、rejectされたPromiseをそのreasonとともに返す。

    
        let tomatoPromise = Promise.reject(`I don't like Tomatos 🍅 `);
    
        /** console log : Promise {<rejected>: "I don't like Tomatos 🍅 "} */
        console.log(tomatoPromise);
    
        /**
        * 結果
        *
        * : Uncaught (in promise) I don't like Tomatos 🍅 
        *
        */
        tomatoPromise.then(val => console.log(val));   
    
    

Application of promises

Promiseの最大のメリットは、タスクを並行して実行できることです。Promiseを使えば、タスクを同時に実行することができるので、時間を大幅に短縮することができます。

ここでは、async awaitを使ってAPI呼び出しを行うことを考えてみましょう。


    async function performingTwoApiCalls (){
        const burgerData = await fetch('/get-api-call-for-burget-data');
        const pizzaData  = await fetch('/get-api-call-for-pizza-data');

        /**
         * 
         * 
         */
        const combineBurgerAndPizzaData = [...burgerData, ...pizzaData];
    }

上の例を見てみると、2回目のAPIコールは最初のAPIコールの終了を待たなければなりません。Promiseを使ってこれを行うより良い方法は以下の通りです。


    function performingTwoApiCalls (){
        const burgerPromise = Promise.resolve(fetch('/get-api-call-for-burget-data'));
        const pizzaPromise  = Promise.resolve(fetch('/get-api-call-for-pizza-data'));

        Promise.all([ burgerPromise , pizzaPromise ])
                .then((val)=>{
                    const burgerData = val[0];
                    const pizzaData  = val[1];

                    const combineBurgerAndPizzaData = [...burgerData, ...pizzaData];
                })

    }

上記の例では、Promise.allメソッドを用いて2つのAPIコールが非同期に実行され、resolveされた値を取得してさらに処理が行われます。