Published on

JavascriptでのPromiseの操作

Authors

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

Javascript の Promises とは何か?

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

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

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

console.log(promiseObj)

/**
 * the output will be Promise {<pending>} not 'some promise'
 */

ある時点で、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)
  }
})

// this will print - '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(/** resolve some suceess way over here */)
  } catch (err) {
    /** reject if some error occured */
    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(
      /**
       * will console the values after 2 seconds
       * [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)
    })
    
    /**
     *
     * output for the above code:
     *
     * 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) {
      //resolved value
      console.log(value)
    })
    // expected output: "🍎"
    
  4. Promise.resolve()

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

    let melonPromise = Promise.resolve(`🍉`)
    
    /** will console log : [object Promise] */
    console.log(melonPromise)
    
    /**
     * if u want to console the value of promise then use a callback
     *
     * This will print 🍉 as output
     */
    melonPromise.then((val) => console.log(val))
    
  5. Promise.reject()

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

    let tomatoPromise = Promise.reject(`I don't like Tomatos 🍅 `)
    
    /** will console log : Promise {<rejected>: "I don't like Tomatos 🍅 "} */
    console.log(tomatoPromise)
    
    /**
     * if u want to console the value of promise then use a callback
     *
     * This will print
     *
     * : Uncaught (in promise) I don't like Tomatos 🍅
     *
     * as output
     */
    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')

  /**
   *
   * some processing with the above 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された値を取得してさらに処理が行われます。