1. ホーム
  2. javascript

promise/deferライブラリはどのように実装されていますか?[クローズド]

2023-12-01 12:32:19

質問

のようなpromise/deferライブラリは、どのように q はどのように実装されているのでしょうか? ソースコードを読もうとしたのですが、かなり理解しにくかったので、NodeやブラウザのようなシングルスレッドJS環境でプロミスを実装するために使われるテクニックは何か、誰かが高いレベルから説明してくれるとうれしいと思いました。

どのように解決するのですか?

例を示すよりも説明する方が難しいと思うので、ここでは defer/promise がどのようなものかを非常に簡単に実装してみます。

免責事項です。 これは機能的な実装ではなく,Promise/A の仕様の一部が欠落しています. これは約束事の基礎を説明するためのものです.

tl;dr: に行ってください。 クラスを作成し、例 セクションで完全な実装を見ることができます。

プロミス

まず、コールバックの配列を持つプロミスオブジェクトを作成する必要があります。オブジェクトで作業を始めるのがわかりやすいからです。

var promise = {
  callbacks: []
}

では、そのメソッドでコールバックを追加します。

var promise = {
  callbacks: [],
  then: function (callback) {
    callbacks.push(callback);
  }
}

そして、エラーのコールバックも必要です。

var promise = {
  okCallbacks: [],
  koCallbacks: [],
  then: function (okCallback, koCallback) {
    okCallbacks.push(okCallback);
    if (koCallback) {
      koCallbacks.push(koCallback);
    }
  }
}

延期する。

ここで、プロミスを持つことになるDeferオブジェクトを作成します。

var defer = {
  promise: promise
};

deferは解決する必要があります。

var defer = {
  promise: promise,
  resolve: function (data) {
    this.promise.okCallbacks.forEach(function(callback) {
      window.setTimeout(function () {
        callback(data)
      }, 0);
    });
  },
};

と拒否する必要があります。

var defer = {
  promise: promise,
  resolve: function (data) {
    this.promise.okCallbacks.forEach(function(callback) {
      window.setTimeout(function () {
        callback(data)
      }, 0);
    });
  },

  reject: function (error) {
    this.promise.koCallbacks.forEach(function(callback) {
      window.setTimeout(function () {
        callback(error)
      }, 0);
    });
  }
};

コードが常に非同期であることを可能にするために、コールバックがタイムアウトで呼び出されることに注意してください。

そして、これが基本的なdefer/promiseの実装に必要なことです。

クラスとサンプルを作成します。

さて、両方のオブジェクトをクラスに変換しましょう。まずは約束です。

var Promise = function () {
  this.okCallbacks = [];
  this.koCallbacks = [];
};

Promise.prototype = {
  okCallbacks: null,
  koCallbacks: null,
  then: function (okCallback, koCallback) {
    okCallbacks.push(okCallback);
    if (koCallback) {
      koCallbacks.push(koCallback);
    }
  }
};

そして、今度は延期です。

var Defer = function () {
  this.promise = new Promise();
};

Defer.prototype = {
  promise: null,
  resolve: function (data) {
    this.promise.okCallbacks.forEach(function(callback) {
      window.setTimeout(function () {
        callback(data)
      }, 0);
    });
  },

  reject: function (error) {
    this.promise.koCallbacks.forEach(function(callback) {
      window.setTimeout(function () {
        callback(error)
      }, 0);
    });
  }
};

そして、使用例です。

function test() {
  var defer = new Defer();
  // an example of an async call
  serverCall(function (request) {
    if (request.status === 200) {
      defer.resolve(request.responseText);
    } else {
      defer.reject(new Error("Status code was " + request.status));
    }
  });
  return defer.promise;
}

test().then(function (text) {
  alert(text);
}, function (error) {
  alert(error.message);
});

見ての通り、基本的な部分はシンプルで小さいものです。他のオプション、例えば複数の約束事の解像度を追加すると、大きくなるでしょう。

Defer.all(promiseA, promiseB, promiseC).then()

またはプロミスチェイニング

getUserById(id).then(getFilesByUser).then(deleteFile).then(promptResult);

仕様の詳細を見るには CommonJS Promiseの仕様 . なお,主要なライブラリ(Q, when.js, rsvp.js, node-promise, ...)は,以下の仕様に準拠しています. プロミス/A の仕様に従います.

十分に明確であったことを望みます。

編集する

コメントで聞かれたように、このバージョンでは2つのことを追加しました。

  • プロミスがどのような状態であっても、プロミスのその後を呼び出すことができるようになりました。
  • プロミスをチェーンする可能性。

解決されたときにプロミスを呼び出せるようにするには、プロミスにステータスを追加し、thenが呼び出されたときにそのステータスをチェックする必要があります。ステータスが解決または拒否された場合、そのデータまたはエラーでコールバックを実行するだけです。

プロミスを連鎖させるためには then を生成し、プロミスが解決/拒否されたときに、コールバックの結果で新しいプロミスを解決/拒否する必要があります。つまり、プロミスが終了したとき、コールバックが新しいプロミスを返した場合、そのプロミスは then() . そうでない場合は、プロミスはコールバックの結果で解決されます。

これがプロミスです。

var Promise = function () {
  this.okCallbacks = [];
  this.koCallbacks = [];
};

Promise.prototype = {
  okCallbacks: null,
  koCallbacks: null,
  status: 'pending',
  error: null,

  then: function (okCallback, koCallback) {
    var defer = new Defer();

    // Add callbacks to the arrays with the defer binded to these callbacks
    this.okCallbacks.push({
      func: okCallback,
      defer: defer
    });

    if (koCallback) {
      this.koCallbacks.push({
        func: koCallback,
        defer: defer
      });
    }

    // Check if the promise is not pending. If not call the callback
    if (this.status === 'resolved') {
      this.executeCallback({
        func: okCallback,
        defer: defer
      }, this.data)
    } else if(this.status === 'rejected') {
      this.executeCallback({
        func: koCallback,
        defer: defer
      }, this.error)
    }

    return defer.promise;
  },

  executeCallback: function (callbackData, result) {
    window.setTimeout(function () {
      var res = callbackData.func(result);
      if (res instanceof Promise) {
        callbackData.defer.bind(res);
      } else {
        callbackData.defer.resolve(res);
      }
    }, 0);
  }
};

そして、繰り下げ。

var Defer = function () {
  this.promise = new Promise();
};

Defer.prototype = {
  promise: null,
  resolve: function (data) {
    var promise = this.promise;
    promise.data = data;
    promise.status = 'resolved';
    promise.okCallbacks.forEach(function(callbackData) {
      promise.executeCallback(callbackData, data);
    });
  },

  reject: function (error) {
    var promise = this.promise;
    promise.error = error;
    promise.status = 'rejected';
    promise.koCallbacks.forEach(function(callbackData) {
      promise.executeCallback(callbackData, error);
    });
  },

  // Make this promise behave like another promise:
  // When the other promise is resolved/rejected this is also resolved/rejected
  // with the same data
  bind: function (promise) {
    var that = this;
    promise.then(function (res) {
      that.resolve(res);
    }, function (err) {
      that.reject(err);
    })
  }
};

ご覧の通り、かなり大きくなりました。