JavaScriptプログラミングにおけるPromiseの使い方の詳しい解釈_基礎知識

WBOY
リリース: 2016-05-16 15:48:50
オリジナル
1173 人が閲覧しました

プロミスコアの説明

Promise には既に独自の仕様がありますが、現在の各 Promise ライブラリは Promise の実装内容に違いがあり、API によっては全く意味が異なっているものもあります。ただし、Promise の核となる内容は同じであり、then メソッドです。これに関連した用語では、Promise は、特定の動作をトリガーできる then メソッドを持つオブジェクトまたは関数を指します。

Promise はさまざまな方法で実装できるため、Promise のコアの説明では特定の実装コードについては説明しません。

最初に Promise のコアの説明を読んでください。これは、コードに記述する方法を検討するためにこの結果を参照してください。
入門: Promise を次のように理解します

Promise が解決する問題を思い出してください。折り返し電話。たとえば、関数 doMission1() は最初のことを表します。このことが完了した後、次のこと doMission2() を実行する必要があります。

まず、一般的なコールバック パターンを見てみましょう。 doMission1() は次のように言いました: 「これをやりたい場合は doMission2() を与えてください。完了したら私が呼び出します。」 したがって、次のようになります。

doMission1(doMission2);

ログイン後にコピー
Promise モードについてはどうですか?あなたは doMission1() に対して次のように言いました。「いいえ、コントロールは私にあります。あなたはそれを変更する必要があります。あなたは最初に特別なものを私に返します。そして私はこの特別なものを使って次のものを手配します。」これは次のようになります:

doMission1().then(doMission2);

ログイン後にコピー
Promise がコールバック モードのマスターとスレーブの関係を変更する (裏返ってマスターになる!) ことがわかります。複数のイベントのプロセス関係が (さまざまなイベントに分散するのではなく) 主要な道路に集中することができます。機能)。

それでは、そのような変換を行うにはどうすればよいでしょうか? doMission1() のコードが次であると仮定して、最も単純なケースから始めましょう:

function doMission1(callback){
  var value = 1;
  callback(value);
}

ログイン後にコピー
その後、次のように変更できます:

function doMission1(){
  return {
    then: function(callback){
      var value = 1;
      callback(value);
    }
  };
}

ログイン後にコピー
これで変換は完了です。実際に役立つ変換ではありませんが、ここでは実際に Promise の最も重要な実装ポイント、つまり Promise が then メソッドを使用して戻り値をオブジェクトに変換することについて触れました。

上級: Q のデザインの旅

延期から開始

design/q0.js は、Q の初期形成の最初のステップです。 Promise を作成するための defer というユーティリティ関数を作成します:

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      value = _value;
      for (var i = 0, ii = pending.length; i < ii; i++) {
        var callback = pending[i];
        callback(value);
      }
      pending = undefined;
    },
    then: function (callback) {
      if (pending) {
        pending.push(callback);
      } else {
        callback(value);
      }
    }
  }
};

ログイン後にコピー
このソース コードからわかるように、defer() を実行すると、resolve と then の 2 つのメソッドを含むオブジェクトが取得されます。 jQuery の Deferred (resolve と then も) を思い出してください。これら 2 つのメソッドは同様の効果があります。次に、保留状態を参照し、待機状態であればコールバックを保存(プッシュ)し、それ以外の場合はすぐにコールバックを呼び出します。 solve は Promise を確認し、値を更新し、保存されているすべてのコールバックを同時に実行します。遅延の使用例は次のとおりです:

var oneOneSecondLater = function () {
  var result = defer();
  setTimeout(function () {
    result.resolve(1);
  }, 1000);
  return result;
};

ログイン後にコピー

oneOneSecondLater().then(callback);

ここで oneOneSecondLater() には非同期コンテンツ (setTimeout) が含まれていますが、ここでは defer() によって生成されたオブジェクトをすぐに返し、非同期終了の最後に (上位値または他の値で) オブジェクトのsolve メソッドを呼び出します。言葉の結果)。

この時点で、上記のコードには問題があります。resolve が複数回実行される可能性があります。したがって、解決が一度だけ有効であることを保証するために、解決にステータスの判断を追加する必要があります。これは Q の次のステップの設計/q1.js です (相違点のみ):

resolve: function (_value) {
  if (pending) {
    value = _value;
    for (var i = 0, ii = pending.length; i < ii; i++) {
      var callback = pending[i];
      callback(value);
    }
    pending = undefined;
  } else {
    throw new Error("A promise can only be resolved once.");
  }
}

ログイン後にコピー
2 回目以降の呼び出しでは、このようなエラーをスローすることも、単に無視することもできます。


延期と約束を分ける

前の実装では、defer によって生成されたオブジェクトには then メソッドとsolve メソッドの両方がありました。定義上、Promise は then メソッドを考慮しますが、Promise をトリガーして状態を変更する解決については別の問題です。したがって、Q は次に then メソッドを使用した Promise と、Resolve を使用した defer を分離し、それぞれを独立して使用します。これは、それぞれの責任を明確にし、特定の権限のみを残すようなものです。これにより、コード ロジックがより明確になり、調整が容易になります。 design/q3.js を参照してください: (q2 はここではスキップされます)

var isPromise = function (value) {
  return value && typeof value.then === "function";
};

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      if (pending) {
        value = _value;
        for (var i = 0, ii = pending.length; i < ii; i++) {
          var callback = pending[i];
          callback(value);
        }
        pending = undefined;
      }
    },
    promise: {
      then: function (callback) {
        if (pending) {
          pending.push(callback);
        } else {
          callback(value);
        }
      }
    }
  };
};

ログイン後にコピー
q1 を注意深く比較すると、その差は非常に小さいことがわかります。一方で、(2 番目以降の解決を直接無視する代わりに) エラーはスローされなくなり、他方では、 then メソッドは Promise という名前のオブジェクトに移動されます。この時点で、 defer() (defer と呼びます) を実行して取得したオブジェクトには、resolve メソッドと、別のオブジェクトを指す Promise 属性が含まれます。この他のオブジェクトは then メソッドのみを備えた Promise です。これで分離が完了します。

先頭にはisPromise()関数もあり、thenメソッド(ダックタイピング判定方法)を持つかどうかでオブジェクトがpromiseであるかどうかを判定します。分離された Promise を正しく使用して処理するには、このように Promise を他の値と区別する必要があります。


Promise カスケードの実装

次のステップは非常に重要です。 Q3 までは、実装された Promise をカスケードすることはできません。しかし、あなたがよく知っている Promise は次のような構文をサポートしているはずです:

promise.then(step1).then(step2);

ログイン後にコピー

以上过程可以理解为,promise将可以创造新的promise,且取自旧的promise的值(前面代码中的value)。要实现then的级联,需要做到一些事情:

  • then方法必须返回promise。
  • 这个返回的promise必须用传递给then方法的回调运行后的返回结果,来设置自己的值。
  • 传递给then方法的回调,必须返回一个promise或值。

design/q4.js中,为了实现这一点,新增了一个工具函数ref:

var ref = function (value) {
  if (value && typeof value.then === "function")
    return value;
  return {
    then: function (callback) {
      return ref(callback(value));
    }
  };
};

ログイン後にコピー

这是在着手处理与promise关联的value。这个工具函数将对任一个value值做一次包装,如果是一个promise,则什么也不做,如果不是promise,则将它包装成一个promise。注意这里有一个递归,它确保包装成的promise可以使用then方法级联。为了帮助理解它,下面是一个使用的例子:

ref("step1").then(function(value){
  console.log(value); // "step1"
  return 15;
}).then(function(value){
  console.log(value); // 15
});

ログイン後にコピー

你可以看到value是怎样传递的,promise级联需要做到的也是如此。

design/q4.js通过结合使用这个ref函数,将原来的defer转变为可级联的形式:

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      if (pending) {
        value = ref(_value); // values wrapped in a promise
        for (var i = 0, ii = pending.length; i < ii; i++) {
          var callback = pending[i];
          value.then(callback); // then called instead
        }
        pending = undefined;
      }
    },
    promise: {
      then: function (_callback) {
        var result = defer();
        // callback is wrapped so that its return
        // value is captured and used to resolve the promise
        // that "then" returns
        var callback = function (value) {
          result.resolve(_callback(value));
        };
        if (pending) {
          pending.push(callback);
        } else {
          value.then(callback);
        }
        return result.promise;
      }
    }
  };
};

ログイン後にコピー

原来callback(value)的形式,都修改为value.then(callback)。这个修改后效果其实和原来相同,只是因为value变成了promise包装的类型,会需要这样调用。

then方法有了较多变动,会先新生成一个defer,并在结尾处返回这个defer的promise。请注意,callback不再是直接取用传递给then的那个,而是在此基础之上增加一层,并把新生成的defer的resolve方法放置在此。此处可以理解为,then方法将返回一个新生成的promise,因此需要把promise的resolve也预留好,在旧的promise的resolve运行后,新的promise的resolve也会随之运行。这样才能像管道一样,让事件按照then连接的内容,一层一层传递下去。
加入错误处理

promise的then方法应该可以包含两个参数,分别是肯定和否定状态的处理函数(onFulfilled与onRejected)。前面我们实现的promise还只能转变为肯定状态,所以,接下来应该加入否定状态部分。

请注意,promise的then方法的两个参数,都是可选参数。design/q6.js(q5也跳过)加入了工具函数reject来帮助实现promise的否定状态:

var reject = function (reason) {
  return {
    then: function (callback, errback) {
      return ref(errback(reason));
    }
  };
};

ログイン後にコピー

它和ref的主要区别是,它返回的对象的then方法,只会取第二个参数的errback来运行。design/q6.js的其余部分是:

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      if (pending) {
        value = ref(_value);
        for (var i = 0, ii = pending.length; i < ii; i++) {
          value.then.apply(value, pending[i]);
        }
        pending = undefined;
      }
    },
    promise: {
      then: function (_callback, _errback) {
        var result = defer();
        // provide default callbacks and errbacks
        _callback = _callback || function (value) {
          // by default, forward fulfillment
          return value;
        };
        _errback = _errback || function (reason) {
          // by default, forward rejection
          return reject(reason);
        };
        var callback = function (value) {
          result.resolve(_callback(value));
        };
        var errback = function (reason) {
          result.resolve(_errback(reason));
        };
        if (pending) {
          pending.push([callback, errback]);
        } else {
          value.then(callback, errback);
        }
        return result.promise;
      }
    }
  };
};

ログイン後にコピー

这里的主要改动是,将数组pending只保存单个回调的形式,改为同时保存肯定和否定的两种回调的形式。而且,在then中定义了默认的肯定和否定回调,使得then方法满足了promise的2个可选参数的要求。

你也许注意到defer中还是只有一个resolve方法,而没有类似jQuery的reject。那么,错误处理要如何触发呢?请看这个例子:

var defer1 = defer(),
promise1 = defer1.promise;
promise1.then(function(value){
  console.log("1: value = ", value);
  return reject("error happens"); 
}).then(function(value){
  console.log("2: value = ", value);
}).then(null, function(reason){
  console.log("3: reason = ", reason);
});
defer1.resolve(10);

// Result:
// 1: value = 10
// 3: reason = error happens

ログイン後にコピー

可以看出,每一个传递给then方法的返回值是很重要的,它将决定下一个then方法的调用结果。而如果像上面这样返回工具函数reject生成的对象,就会触发错误处理。
融入异步

终于到了最后的design/q7.js。直到前面的q6,还存在一个问题,就是then方法运行的时候,可能是同步的,也可能是异步的,这取决于传递给then的函数(例如直接返回一个值,就是同步,返回一个其他的promise,就可以是异步)。这种不确定性可能带来潜在的问题。因此,Q的后面这一步,是确保将所有then转变为异步。

design/q7.js定义了另一个工具函数enqueue:

var enqueue = function (callback) {
  //process.nextTick(callback); // NodeJS
  setTimeout(callback, 1); // Na&#63;ve browser solution
};

ログイン後にコピー

显然,这个工具函数会将任意函数推迟到下一个事件队列运行。

design/q7.js其他的修改点是(只显示修改部分):

var ref = function (value) {
  // ...
  return {
    then: function (callback) {
      var result = defer();
      // XXX
      enqueue(function () {
        result.resolve(callback(value));
      });
      return result.promise;
    }
  };
};

var reject = function (reason) {
  return {
    then: function (callback, errback) {
      var result = defer();
      // XXX
      enqueue(function () {
        result.resolve(errback(reason));
      });
      return result.promise;
    }
  };
};

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      // ...
          enqueue(function () {
            value.then.apply(value, pending[i]);
          });
      // ...
    },
    promise: {
      then: function (_callback, _errback) {
          // ...
          enqueue(function () {
            value.then(callback, errback);
          });
          // ...
      }
    }
  };
};

ログイン後にコピー

即把原来的value.then的部分,都转变为异步。

到此,Q提供的Promise设计原理q0~q7,全部结束。
结语

即便本文已经是这么长的篇幅,但所讲述的也只到基础的Promise。大部分Promise库会有更多的API来应对更多和Promise有关的需求,例如all()、spread(),不过,读到这里,你已经了解了实现Promise的核心理念,这一定对你今后应用Promise有所帮助。

在我看来,Promise是精巧的设计,我花了相当一些时间才差不多理解它。Q作为一个典型Promise库,在思路上走得很明确。可以感受到,再复杂的库也是先从基本的要点开始的,如果我们自己要做类似的事,也应该保持这样的心态一点一点进步。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート