0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

JavaScriptのPromiseについて part2

Last updated at Posted at 2022-08-17

初めに

今回はプロミスの静的メソッド、コールバックの内部関数をプロミスへの変換について、気になるところをまとめていきたいと思います。

今回の参考文章はこちらです。
Promise API - javascript.info
Promisification - javascript.info

Promise API

Promise.all(iterable)

使い方
  • iterable:通常はPromiseの入った配列。
  • 配列に全てのPromiseがresolveされるまで待ち(fulfilled状態)、新しいPromiseオブジェクトを格納した配列を返す。(then()の引数となる)
注意点
  • 配列にrejectされたPromiseが出た場合、全体としてrejectされる。
    • 残りのPromiseが実行するが、Promise.all()の返り値Promiseはrejectになり、配列にほかに実行完了のPromise結果を無視する。
  • Promise.all()は繰り返し可能の配列にPromiseオブジェクトしか受け入れませんが、そうでない場合はPromise.resolve()でラップしたものと扱われる。

Promise.allSettled(iterable)

使い方
  • iterablePromiseオブジェクトを格納した配列。
  • Promise.allSettled()はresolveされる(fulfilled状態)かrejectされる(rejected状態)に関わらず、すべてのPromiseオブジェクトが完了したら新しいPromise配列を返す。
  • 配列にある各Promiseオブジェクトから.statusで状態をチェックしたり、履行したPromiseなら.valueから値を、拒否したPromiseなら.reasonからエラーオブジェクトを取得する。

Promise.race(iterable)

Promise.race([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
  3,
  new Promise((resolve, reject) => resolve(4)),
]).then(result => {
  console.log(result)
})

// 3
使い方
  • iterablePromiseオブジェクトを格納した配列。
  • 最初の完了した値またはエラーを返します。それ以外は無視する。

Promise.any(iterable)

使い方
  • iterablePromiseオブジェクトを格納した配列。
  • 最初の履行した(fulfilledPromiseを待ち、その結果を返す。
  • すべてのPromiseが拒否(rejected)ならAggregateErrorエラーオブジェクトを返す。errorsプロパティでエラーメッセージを取得できる。

Promise.resolve(value)/reject(error)

使い方
  • value:任意の値
  • Promise.resolve()は値をラップし、解決(resolve)されたPromiseオブジェクトを返す。
  • Promise.reject()で拒否(reject)されたエラーのPromiseオブジェクトを返す。

Promisification

callback関数からPromiseを返す関数へ変換することです。

 シンプルな例 です。

function loadScript(src, callback) {
  let script = document.createElement('script')
  script.src = src

  script.onload = () => callback(null, script)
  script.onerror = () => callback(new Error(`Script load error: ${src}`))

  document.head.append(script)
}

上はコールバック、下はPromiseを返す。

function loadScript(src) {
  return new Promise(resolve, reject => {
    let script = document.createElement('script')
    script.src = src

    script.onload = () => resolve(script)
    script.onerror = () => reject(new Error(`Script load error: ${src}`))

    document.head.append(script)
  })
}

let promise = loadScript('http://....')
promise.then(
  script => console.log(`${script.src} is loaded`),
  error => console.log(`Error: ${error.message}`)
)

基本的には関数をPromiseでラップして、Promiseの中にresolveかrejectを通してPromiseオブジェクト(あるいはエラーオブジェクト)を生成させ、その結果をthenチェーンへ渡すプロセスです。可読性を高め、コントロールもしやすくなる方法です。

ほかも このように 書き換えてもいいらしいです。

let loadScriptPromise = function (src) {
  return new Promise((resolve, reject) => {
    loadScript(src, (err, script) => {
      if (err) {
        return reject(err)
      } else {
        resolve(script)
      }
    })
  })
}

loadScriptは最初のコールバックsrccallback(ここでは中身はerrscript)、関数の結果がPromiseを返すように全体をPromiseでラップし、errが存在する場合はreject()を呼び出しerrの結果をPromiseで返すか、resolve()scriptを返す。

しかし毎回のように書き直すにはさすがに手間がかかるし、ラッパーが必要であれば別の関数(ヘルパー)でPromise化してくれると楽になります。

function promisify(f) {
  // at last, return the wrapped Promise to outside
  return function (...args) {
    return new Promise((resolve, reject) => {
      function callback(err, result) {
        if (err) {
          return reject(err)
        } else {
          resolve(result)
        }
      }

      // push the promisifying callback function to the end of args
      args.push(callback)

      // call the original function, set this promisifying function in it, and spread function in args
      f.call(this, ...args)
    })
  }
}

(このコード一番下についてどうしてこう書くのか充分に理解していません。憶測のうえ解釈が適切かどうかもわかりません。とりあえずメモとして残ります。)

関数promisifyのすることが簡潔に言うと、Promisecallback関数をラップして返す。
fはもとの関数で、argsは後からラップされたcallbackの保管場所で、この匿名関数ではPromiseでラップした関数をargsの最後に入れて、fという元の関数に今にいるPromise環境を呼び出し、argsで保管されている関数を展開して入れます。って言ってもとても理解しづらい解釈の仕方だと思いますが。

順番を変えて一番中から見ていくと、Promiseでラップした関数を最後にpromisify()の返り値匿名関数の引数にcallbackを入れておいて、それからPromise化したい関数fcallで強制的に今のPromiseコンテキストに変えさせて、f関数の引数に、argsに貯蔵したcallbackを展開して入れる。

説明ではf.call(this, ...args)の作用はcallbackで結果を追跡するって書いてありますが、私にはちょっと難しすぎてまだ理解できていません...。
確かにfの環境が一時的にPromise化するためにcallが必要、callbackPromise環境に依存していると分かっています。でもどうやって結果を追跡できるのかまだわかりません。ちゃんと動かせる環境ができたら補足しようと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?