如何实施promise / defer库?

时间:2013-07-18 08:44:38

标签: javascript promise

如何实施承诺/推迟库q?我试图阅读源代码,但发现它很难理解,所以我认为如果有人能从高层次向我解释在单线程JS环境中用于实现promise的技术是多么好比如节点和浏览器。

5 个答案:

答案 0 :(得分:149)

我发现解释比展示一个例子更难,所以这里有一个非常简单的延迟/承诺的实现。

免责声明:这不是功能实现,并且缺少Promise / A规范的某些部分,这只是为了解释承诺的基础。

tl; dr:转到创建课程和示例部分以查看完整实施。

无极:

首先,我们需要创建一个带有回调数组的promise对象。我将开始使用对象,因为它更清晰:

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

推迟:

现在创建一个具有承诺的延迟对象:

var defer = {
  promise: promise
};

延迟需要解决:

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

请注意,在超时时调用回调以允许代码始终是异步的。

这就是基本推迟/承诺实施所需要的。

创建类和示例:

现在让我们将两个对象转换为类,首先是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 Specification。请注意,主库(Q,when.js,rsvp.js,node-promise,...)遵循Promises/A规范。

希望我足够清楚。

编辑:

正如评论中所说,我在这个版本中添加了两件事:

  • 无论身处何种状态,都可以随时致电承诺。
  • 链接承诺的可能性。

为了能够在解决时调用promise,您需要将状态添加到promise中,并在调用then时检查该状态。如果状态已解决或被拒绝,则只需使用其数据或错误执行回调。

为了能够链接承诺,您需要为每次调用then生成新的延迟,并且当承诺被解决/拒绝时,使用回调结果解析/拒绝新承诺。因此,当promise完成时,如果回调返回一个新的promise,它将绑定到then()返回的promise。如果没有,则使用回调结果解决承诺。

这是承诺:

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

正如你所看到的,它已经增长了很多。

答案 1 :(得分:7)

Q在实现方面是一个非常复杂的promise库,因为它旨在支持流水线和RPC类型场景。我有自己的Promises/A+规范here的非常简单的实现。

原则上它非常简单。在解决/解决了promise之前,通过将它们推入数组来保留任何回调或错误的记录。当承诺得到解决后,您可以调用适当的回调或错误,并记录承诺的结果(以及是否已履行或拒绝)。在它结算之后,你只需用存储的结果调用回调或错误回复。

这大概给出了done的语义。要构建then,您只需返回一个新的promise,它会通过调用callbacks / errbacks来解析。

如果您有兴趣全面阐述完整的承诺实施背后的原因,并支持RPC和像Q这样的流水线,那么您可以阅读kriskowal的推理here。这是一个非常好的渐进式方法,如果您正在考虑实施承诺,我不能给予足够高的推荐。即使你只是要使用诺言库,它也许值得一读。

答案 2 :(得分:6)

正如福布斯在他的回答中提到的那样,我记录了制作像Q这样的图书馆所涉及的许多设计决策,https://github.com/kriskowal/q/tree/v1/design。可以这么说,有一个承诺库的级别,以及许多停止在不同级别的库。

在第一级,由Promises / A +规范捕获,承诺是最终结果的代理,适用于管理“本地异步”。也就是说,它适合于确保以正确的顺序进行工作,并且确保操作的结果简单直接,无论其是否已经结算,或将来是否会发生。它也使得一方或多方订阅最终结果同样简单。

问:我已经实现了它,它提供了最终,远程或最终+远程结果代理的承诺。为此,它的设计是颠倒的,具有承诺延迟承诺的不同实现,履行承诺,拒绝承诺和远程对象的承诺(最后一个在Q-Connection中实现)。它们都共享相同的接口,并通过发送和接收“then”(这对Promises / A +已足够)以及“get”和“invoke”等消息来工作。所以,Q是关于“分布式异步”,并存在于另一层。

然而,Q实际上是从较高层取下来的,其中承诺用于管理相互可疑的各方之间的分布式异步,例如您,商家,银行,Facebook,政府 - 而不是敌人,甚至可能是朋友,但有时会产生利益冲突。我实现的Q旨在与硬件安全承诺API兼容(这是分离promiseresolve)的原因,希望它能引入人们的承诺,培训他们使用这个API,如果他们将来需要在安全的mashup中使用promises,就允许他们使用它们的代码。

当然,在向上移动图层时需要权衡利弊,通常是在速度上。因此,promises实现也可以设计为共存。这就是“thatable”的概念进入的地方。每层的Promise库可以设计为使用来自任何其他层的promise,因此多个实现可以共存,用户只能购买他们需要的东西。

所有这些都说,没有理由难以阅读。 Domenic和我正在开发一个Q版本,它将更加模块化和平易近人,其中一些分散注意力的依赖关系和解决方案转移到其他模块和包中。值得庆幸的是,ForbesCrockford等人通过制作更简单的库来填补教育空白。

答案 3 :(得分:4)

首先要确保你了解Promise应该如何运作。请查看CommonJs Promises proposalsPromises/A+ specification

可以通过几个简单的行来实现两个基本概念:

  • 使用结果异步解析Promise。添加回调是一个透明的操作 - 与承诺是否已经解决无关,一旦可用,它们将被调用。

    function Deferred() {
        var callbacks = [], // list of callbacks
            result; // the resolve arguments or undefined until they're available
        this.resolve = function() {
            if (result) return; // if already settled, abort
            result = arguments; // settle the result
            for (var c;c=callbacks.shift();) // execute stored callbacks
                c.apply(null, result);
        });
        // create Promise interface with a function to add callbacks:
        this.promise = new Promise(function add(c) {
            if (result) // when results are available
                c.apply(null, result); // call it immediately
            else
                callbacks.push(c); // put it on the list to be executed later
        });
    }
    // just an interface for inheritance
    function Promise(add) {
        this.addCallback = add;
    }
    
  • Promise有then方法允许链接它们。我接受一个回调并返回一个新的Promise,它将在使用第一个promise的结果调用后用该回调的结果解析。如果回调返回Promise,它将被同化而不是嵌套。

    Promise.prototype.then = function(fn) {
        var dfd = new Deferred(); // create a new result Deferred
        this.addCallback(function() { // when `this` resolves…
            // execute the callback with the results
            var result = fn.apply(null, arguments);
            // check whether it returned a promise
            if (result instanceof Promise)
                result.addCallback(dfd.resolve); // then hook the resolution on it
            else
                dfd.resolve(result); // resolve the new promise immediately 
            });
        });
        // and return the new Promise
        return dfd.promise;
    };
    

进一步的概念是维护一个单独的错误状态(带有额外的回调)并捕获处理程序中的异常,或保证回调的异步性。一旦你添加了这些,你就有了一个功能齐全的Promise实现。

这是写出错误的东西。不幸的是,这很重复;你可以通过使用额外的闭包来做得更好但是它真的很难理解。

function Deferred() {
    var callbacks = [], // list of callbacks
        errbacks = [], // list of errbacks
        value, // the fulfill arguments or undefined until they're available
        reason; // the error arguments or undefined until they're available
    this.fulfill = function() {
        if (reason || value) return false; // can't change state
        value = arguments; // settle the result
        for (var c;c=callbacks.shift();)
            c.apply(null, value);
        errbacks.length = 0; // clear stored errbacks
    });
    this.reject = function() {
        if (value || reason) return false; // can't change state
        reason = arguments; // settle the errror
        for (var c;c=errbacks.shift();)
            c.apply(null, reason);
        callbacks.length = 0; // clear stored callbacks
    });
    this.promise = new Promise(function add(c) {
        if (reason) return; // nothing to do
        if (value)
            c.apply(null, value);
        else
            callbacks.push(c);
    }, function add(c) {
        if (value) return; // nothing to do
        if (reason)
            c.apply(null, reason);
        else
            errbacks.push(c);
    });
}
function Promise(addC, addE) {
    this.addCallback = addC;
    this.addErrback = addE;
}
Promise.prototype.then = function(fn, err) {
    var dfd = new Deferred();
    this.addCallback(function() { // when `this` is fulfilled…
        try {
            var result = fn.apply(null, arguments);
            if (result instanceof Promise) {
                result.addCallback(dfd.fulfill);
                result.addErrback(dfd.reject);
            } else
                dfd.fulfill(result);
        } catch(e) { // when an exception was thrown
            dfd.reject(e);
        }
    });
    this.addErrback(err ? function() { // when `this` is rejected…
        try {
            var result = err.apply(null, arguments);
            if (result instanceof Promise) {
                result.addCallback(dfd.fulfill);
                result.addErrback(dfd.reject);
            } else
                dfd.fulfill(result);
        } catch(e) { // when an exception was re-thrown
            dfd.reject(e);
        }
    } : dfd.reject); // when no `err` handler is passed then just propagate
    return dfd.promise;
};

答案 4 :(得分:1)

您可能想查看Adehun上的blog post

Adehun是一个非常轻量级的实现(大约166 LOC),对于学习如何实现Promise / A +规范非常有用。

免责声明:我撰写了博文,但博客文章确实解释了所有关于Adehun的内容。

过渡功能 - 状态转换的关守

关守功能;确保在满足所有必需条件时发生状态转换。

如果满足条件,此函数将更新promise的状态和值。然后它会触发流程功能以进行进一步处理。

流程功能根据转换执行正确的操作(例如,待完成)并在稍后解释。

function transition (state, value) {
  if (this.state === state ||
    this.state !== validStates.PENDING ||
    !isValidState(state)) {
      return;
    }

  this.value = value;
  this.state = state;
  this.process();
}

然后功能

then函数接受两个可选参数(onFulfill和onReject处理程序)并且必须返回一个新的promise。两个主要要求:

  1. 基本承诺(调用它的那个)需要使用传入的处理程序创建一个新的承诺; base还存储对此创建的promise的内部引用,以便在履行/拒绝基础承诺后调用它。

  2. 如果确定了基本承诺(即履行或拒绝),则应立即调用相应的处理程序。 Adehun.js通过在then函数中调用process来处理这种情况。

  3. ``

    function then(onFulfilled, onRejected) {
        var queuedPromise = new Adehun();
        if (Utils.isFunction(onFulfilled)) {
            queuedPromise.handlers.fulfill = onFulfilled;
        }
    
        if (Utils.isFunction(onRejected)) {
            queuedPromise.handlers.reject = onRejected;
        }
    
        this.queue.push(queuedPromise);
        this.process();
    
        return queuedPromise;
    }`
    

    处理功能 - 处理转换

    在状态转换后或调用then函数时调用它。因此,它需要检查挂起的promise,因为它可能是从then函数调用的。

    进程对所有内部存储的promise(即通过then函数附加到基本promise的那些)运行Promise Resolution过程,并强制执行以下Promise / A +要求:

    1. 使用Utils.runAsync助手异步调用处理程序(围绕setTimeout的一个瘦包装器(setImmediate也可以))。

    2. 为onSuccess和onReject处理程序创建后备处理程序(如果它们丢失)。

    3. 根据承诺状态选择正确的处理函数,例如:履行或拒绝。

    4. 将处理程序应用于基础promise的值。此操作的值将传递给Resolve函数以完成承诺处理周期。

    5. 如果发生错误,则会立即拒绝附加的承诺。

      function process(){     var that = this,         fulfillFallBack = function(value){             回报值;         },         rejectFallBack = function(reason){             抛出理由;         };

      if (this.state === validStates.PENDING) {
          return;
      }
      
      Utils.runAsync(function() {
          while (that.queue.length) {
              var queuedP = that.queue.shift(),
                  handler = null,
                  value;
      
              if (that.state === validStates.FULFILLED) {
                  handler = queuedP.handlers.fulfill ||
                      fulfillFallBack;
              }
              if (that.state === validStates.REJECTED) {
                  handler = queuedP.handlers.reject ||
                      rejectFallBack;
              }
      
              try {
                  value = handler(that.value);
              } catch (e) {
                  queuedP.reject(e);
                  continue;
              }
      
              Resolve(queuedP, value);
          }
      });
      

      }

    6. 解决功能 - 解决承诺

      这可能是promise实现中最重要的部分,因为它处理了promise解析。它接受两个参数 - 承诺及其分辨率值。

      虽然对各种可能的分辨率值进行了大量检查;有趣的解决方案是两个 - 涉及传入的promise和一个thenable(一个具有then值的对象)。

      1. 传递承诺价值
      2. 如果分辨率值是另一个承诺,则承诺必须采用此分辨率值的状态。由于此分辨率值可以挂起或已解决,因此最简单的方法是将新的处理程序附加到解析值并在其中处理原始承诺。无论何时结算,原始承诺都将得到解决或拒绝。

        1. 传递一个值得的值
        2. 这里的问题是,thenable值的then函数必须只被调用一次(很好地用于函数式编程的一次包装器)。同样,如果then函数的检索抛出异常,则会立即拒绝承诺。

          与之前一样,使用最终解析或拒绝promise的函数调用then函数,但这里的差异是在第一次调用时设置的被调用标志,并且后续调用是无操作。

          function Resolve(promise, x) {
            if (promise === x) {
              var msg = "Promise can't be value";
              promise.reject(new TypeError(msg));
            }
            else if (Utils.isPromise(x)) {
              if (x.state === validStates.PENDING){
                x.then(function (val) {
                  Resolve(promise, val);
                }, function (reason) {
                  promise.reject(reason);
                });
              } else {
                promise.transition(x.state, x.value);
              }
            }
            else if (Utils.isObject(x) ||
                     Utils.isFunction(x)) {
              var called = false,
                  thenHandler;
          
              try {
                thenHandler = x.then;
          
                if (Utils.isFunction(thenHandler)){
                  thenHandler.call(x,
                    function (y) {
                      if (!called) {
                        Resolve(promise, y);
                        called = true;
                      }
                    }, function (r) {
                      if (!called) {
                        promise.reject(r);
                        called = true;
                      }
                 });
               } else {
                 promise.fulfill(x);
                 called = true;
               }
             } catch (e) {
               if (!called) {
                 promise.reject(e);
                 called = true;
               }
             }
           }
           else {
             promise.fulfill(x);
           }
          }
          

          承诺构造函数

          这就是把它放在一起的那个。履行和拒绝函数是通过无操作函数来解析和拒绝的语法糖。

          var Adehun = function (fn) {
           var that = this;
          
           this.value = null;
           this.state = validStates.PENDING;
           this.queue = [];
           this.handlers = {
             fulfill : null,
             reject : null
           };
          
           if (fn) {
             fn(function (value) {
               Resolve(that, value);
             }, function (reason) {
               that.reject(reason);
             });
           }
          };
          

          我希望这有助于为承诺的工作方式提供更多的启示。