Heim  >  Artikel  >  Web-Frontend  >  Detaillierte Interpretation der Verwendung von Promise in der JavaScript-Programmierung_Grundkenntnisse

Detaillierte Interpretation der Verwendung von Promise in der JavaScript-Programmierung_Grundkenntnisse

WBOY
WBOYOriginal
2016-05-16 15:48:501167Durchsuche

Kernbeschreibung des Versprechens

Obwohl Promise bereits über eigene Spezifikationen verfügt, weisen die aktuellen verschiedenen Promise-Bibliotheken Unterschiede in den Implementierungsdetails von Promise auf, und einige APIs haben sogar völlig unterschiedliche Bedeutungen. Der Kerninhalt von Promise ist jedoch derselbe, es ist die Then-Methode. In verwandten Begriffen bezieht sich ein Versprechen auf ein Objekt oder eine Funktion, die über eine then-Methode verfügt, die ein bestimmtes Verhalten auslösen kann.

Promise kann auf unterschiedliche Weise implementiert werden, daher wird in der Promise-Kernbeschreibung kein spezifischer Implementierungscode erläutert.

Lesen Sie zuerst die Kernbeschreibung von Promise. Es bedeutet: Sehen Sie, dies ist das Ergebnis, das geschrieben werden muss. Bitte beziehen Sie sich auf dieses Ergebnis, um darüber nachzudenken, wie es in Code geschrieben wird.
Erste Schritte: Verstehen Sie Promise auf diese Weise

Erinnern Sie sich, welches Problem Promise löst? Rückruf. Zum Beispiel stellt die Funktion doMission1() die erste Sache dar. Nun wollen wir die nächste Sache tun, doMission2(), nachdem diese Sache abgeschlossen ist.

Werfen wir zunächst einen Blick auf unsere gängigen Rückrufmuster. doMission1() sagte: „Wenn Sie dies tun möchten, geben Sie mir doMission2() und ich werde es für Sie aufrufen, nachdem es fertig ist.“

doMission1(doMission2);

Was ist mit dem Promise-Modus? Sie sagten zu doMission1(): „Nein, die Kontrolle liegt bei mir. Sie sollten sie ändern. Sie geben mir zuerst eine besondere Sache zurück, und dann werde ich diese Sache verwenden, um die nächste Sache zu arrangieren.“ das wird so aussehen:

doMission1().then(doMission2);

Es ist ersichtlich, dass Promise die Master-Slave-Beziehung des Rückrufmodus ändert (drehen Sie sich um und werden Sie zum Master!). Die Prozessbeziehung mehrerer Ereignisse kann auf die Hauptstraße konzentriert werden (anstatt auf verschiedene Ereignisse verteilt zu werden). Funktionen).

Okay, wie macht man so eine Konvertierung? Beginnen wir mit dem einfachsten Fall, vorausgesetzt, der Code von doMission1() lautet:

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

Dann kann es so geändert werden, dass es so aussieht:

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

Damit ist die Konvertierung abgeschlossen. Obwohl es sich nicht um eine wirklich nützliche Konvertierung handelt, haben wir hier tatsächlich den wichtigsten Implementierungspunkt von Promise angesprochen, nämlich dass Promise den Rückgabewert mit einer then-Methode in ein Objekt konvertiert.

Fortgeschritten: Qs Designreise

Beginnen Sie mit der Verzögerung

design/q0.js ist der erste Schritt bei der anfänglichen Gestaltung von Q. Es erstellt eine Hilfsfunktion namens „defer“ zum Erstellen von Promise:

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);
      }
    }
  }
};

Wie aus diesem Quellcode ersichtlich ist, wird beim Ausführen von defer() ein Objekt abgerufen, das zwei Methoden enthält: „resolve“ und „dann“. Bitte erinnern Sie sich an jQuerys Deferred (auch „resolve“ und „dann“). Diese beiden Methoden haben ähnliche Auswirkungen. Dann wird auf den ausstehenden Status verwiesen, und wenn er sich im Wartezustand befindet, wird der Rückruf gespeichert (Push), andernfalls wird der Rückruf sofort aufgerufen. „resolve“ bestätigt das Versprechen, aktualisiert den Wert und führt alle gespeicherten Rückrufe gleichzeitig aus. Beispiele für die Verwendung von „defer“ sind wie folgt:

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

oneOneSecondLater().then(callback);

Hier enthält oneOneSecondLater() asynchronen Inhalt (setTimeout), aber hier gibt es sofort ein von defer() generiertes Objekt zurück und ruft dann am Ende des asynchronen Endes (mit dem oberen Wert oder anders) die Auflösungsmethode des Objekts auf Wörter resultieren).

An diesem Punkt gibt es ein Problem mit dem obigen Code: „resolve“ kann mehrmals ausgeführt werden. Daher sollte die Beurteilung des Status zum Beschluss hinzugefügt werden, um sicherzustellen, dass der Beschluss nur einmal gültig ist. Dies ist das design/q1.js des nächsten Schritts von Q (nur der Differenzteil):

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.");
  }
}

Beim zweiten und den folgenden Aufrufen können Sie einen Fehler wie diesen auslösen oder ihn einfach ignorieren.


Aufschub und Versprechen trennen

In der vorherigen Implementierung verfügt das von defer generierte Objekt sowohl über die Then-Methode als auch über die Resolution-Methode. Per Definition kümmert sich Promise um die Methode then. Was die Entschlossenheit betrifft, die das Versprechen auslöst, den Zustand zu ändern, ist das eine andere Sache. Daher wird Q als nächstes das Promise mit der then-Methode und das defer mit der Resolution trennen und beide unabhängig voneinander verwenden. Dies ist so, als würden die jeweiligen Verantwortlichkeiten geklärt und nur bestimmte Berechtigungen beibehalten. Dadurch wird die Codelogik klarer und einfacher anzupassen. Bitte sehen Sie sich design/q3.js an: (q2 wird hier übersprungen)

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);
        }
      }
    }
  };
};

Wenn Sie Q1 sorgfältig vergleichen, werden Sie feststellen, dass der Unterschied sehr gering ist. Einerseits wird der Fehler nicht mehr geworfen (anstatt die zweite und weitere Auflösungen direkt zu ignorieren), andererseits wird die then-Methode auf ein Objekt namens Promise verschoben. Zu diesem Zeitpunkt verfügt das durch Ausführen von defer() erhaltene Objekt (nennen wir es defer) über eine Auflösungsmethode und ein Versprechensattribut, das auf ein anderes Objekt verweist. Dieses andere Objekt ist ein Versprechen mit nur einer then-Methode. Damit ist die Trennung abgeschlossen.

Es gibt auch eine Funktion isPromise(), die bestimmt, ob das Objekt ein Versprechen ist, indem es eine then-Methode (Duck-Typing-Beurteilungsmethode) hat. Um losgelöste Versprechen richtig verwenden und handhaben zu können, müssen Sie Versprechen von anderen Werten wie diesem unterscheiden.


Versprechenskaskade implementieren

Der nächste Schritt wird sehr wichtig sein. Bis zum dritten Quartal können die umgesetzten Versprechen nicht kaskadiert werden. Aber die Versprechen, mit denen Sie vertraut sind, sollten eine Syntax wie diese unterstützen:

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库,在思路上走得很明确。可以感受到,再复杂的库也是先从基本的要点开始的,如果我们自己要做类似的事,也应该保持这样的心态一点一点进步。

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn