JS - 序列中的链异步方法没有回调或修改

时间:2016-08-19 00:29:48

标签: javascript node.js asynchronous promise

我试图添加"默认回调"一个原型,如果没有提供回调函数(以promise的形式)给异步方法。

目标是让一个类的异步方法链同步运行

Item.async1().async2()....asyncN()

请注意,异步函数本身期望回调,但它们不会在函数调用中作为参数传递(这告诉我在回调查找失败时类需要默认行为)

Spec声明我无法直接修改原型方法的行为或副作用。我可以添加原型方法。我们无法了解这些原型方法的实现方式。

TLDR:如果不修改原型方法,如何链接N个异步方法并确保它们按顺序运行?

BTW:如果我想实现已宣传的版本,那么宣传有问题的原型方法会有所帮助,但看起来我们已经限制在原始函数调用

3 个答案:

答案 0 :(得分:7)

好吧,我没有回答 - 但我was challenged

很容易利用承诺的内置功能,以便免费获得这种排队。以下是此转换的工作原理:

  • 我们将回调API转换为带有promise子类的新对象的promise。
  • 我们将所有承诺的方法添加到子类本身 - 所以它链。
    • 我们告诉子类执行then中的所有方法,因此它们排队为then是承诺所具有的排队机制。

注意:我在这里写的promisifypromisifyAll方法 - 您应该抓住NPM - 许多使用承诺构造函数的好的和快速的用法。

首先,我们需要一种converts a callback API to promises

的方法
// F is a promise subclass
function promisify(fn) { // take a function
    return function(...args) {  // return a new one with promises
      return new F((resolve, reject) => { // that returns a promise
         // that calls the original function and resolves the promise
         fn.call(this, ...args, (err, data) => err ? reject(err) : resolve(data));
      });
    };
  } 

现在,让我们宣传整个对象:

  function promisifyAll(obj) {
    const o = {};
    for(const prop in obj) {
      if(!(obj[prop].call)) continue; // only functions
      o[prop] = promisify(obj[prop]).bind(obj);
    }
    return o;
  }

到目前为止,没有什么新的,很多NPM库这样做 - 现在为了承诺的魔力 - 让我们创建一个方法来执行then中原始对象的函数:

function whenReadyAll(obj) {
    const obj2 = {}; // create a new object
    for(const prop in obj) { // for each original object
       obj2[prop] = function(...args) { 
         // return a function that does the same thing in a `then`
         return this.then(() => obj[prop](...args));
       };
    }
    return obj2;
  }

现在,让我们结束

function liquidate(obj) {
  const promised = promisifyAll(obj); // convert the object to a promise API
  class F extends Promise {} // create a promise subclass
  Object.assign(F.prototype, whenReadyAll(promised)); // add the API to it
  return promised; // return it
  // previous code here
}

就是这样,如果我们希望这个例子是自包含的(再次,承诺和promisifyAll通常由一个库提供):

function liquidate(obj) {
  const promised = promisifyAll(obj);
  class F extends Promise {}
  Object.assign(F.prototype, whenReadyAll(promised)); // add the API  
  return promised;
  function whenReadyAll(obj) {
    const obj2 = {};
    for(const prop in obj) {
       obj2[prop] = function(...args) { 
         return this.then(() => obj[prop](...args));
       };
    }
    return obj2;
  }
  function promisifyAll(obj) {
    const o = {};
    for(const prop in obj) {
      if(!(obj[prop].call)) continue; // only functions
      o[prop] = promisify(obj[prop]).bind(obj);
    }
    return o;
  }
  function promisify(fn) {
    return function(...args) { 
      return new F((resolve, reject) => {
         fn.call(this, ...args, (err, data) => err ? reject(err) : resolve(data));
      });
    };
  } 
}

或者使用可以宣传的图书馆:

function liquidate(obj) { // 14 LoC
  class F extends Promise {} 
  const promised = promisifyAll(obj, F); // F is the promise impl
  Object.assign(F.prototype, whenReadyAll(promised)); // add the API  
  return promised;
  function whenReadyAll(obj) {
    const obj2 = {};
    for(const prop in obj) {
       obj2[prop] = function(...args) { 
         return this.then(() => obj[prop](...args));
       };
    }
    return obj2;
  }
}

没有演示的答案是什么:

var o = {  // object with a delay callback method
  delay(cb) { 
    console.log("delay"); 
    setTimeout(() => cb(null), 1000); 
  }
};
var o2 = liquidate(o); // let's liquidate it
// and we even get `then` for free, so we can verify this works
var p = o2.delay().then(x => console.log("First Delay!")).
                   delay().
                   then(x => console.log("Second Delay!"));

// logs delay, then First Delay! after a second, 
// then delay and then Second Delay! after a second

将此粘贴​​粘贴到友好的邻居控制台,亲眼看看:)

要证明这可以保留原始对象的状态(很容易将其修改为不符合要求)让我们添加i变量并在延迟时增加它看到事情有效:

var o = {  // object with a delay callback method
  delay(cb) { 
    console.log("delay", this.i++); 
    setTimeout(() => cb(null), 1000); 
  },
  i: 0
};
var o2 = liquidate(o); // let's liquidate it
// and we even get `then` for free, so we can verify this works
var p = o2.delay().then(x => console.log("First Delay!")).
                   delay().
                   then(x => console.log("Second Delay!", o.i));
//logs:
// delay 0
// First Delay!
// delay 1
// Second Delay! 2

答案 1 :(得分:2)

如果已经提供了.async1().async2()并且他们需要回调并且您不允许修改它们,则无法实现Item.async1().async2()....asyncN()。你正在调用的方法不是以这种方式工作而构建的,如果你不允许更改它们,那么除了用你想要的方法替换那些方法之外你没有什么可做的。

如果您可以创建具有内部使用原始方法的自己名称的新方法,那么就可以完成。一个如何做到这一点的模型是jQuery动画。他们允许你做这样的事情:

$("#progress").slideDown(300).delay(1000).slideUp(300);

每个异步操作都将链接在一起。 jQuery通过执行以下操作来实现:

  1. 每个方法都返回原始对象,因此对象上任何方法的链接都可以使用。
  2. 如果异步操作已在运行,则每个被调用的新异步方法都会进入队列(在对象上)以及该方法的参数。
  3. 每种方法的底层实现可以使用与传统回调(或承诺)的异步操作。操作完成后,它会检查队列以查看是否有更多操作要运行,如果是,则从队列中启动下一个操作并将其从队列中删除。
  4. 如果在另一个正在运行时调用新的异步操作,它们将再次添加到队列的末尾。每个对象(或您想要序列化的任何项目组)都有自己的队列。
  5. 因此,如果期望回调的原始异步方法是.async1().async2(),您可以创建.async1Chain().async2Chain(),这样就可以使其工作如下:

    Item.async1Chain().async2Chain()....asyncNChain()
    

    在内部,.async1Chain()将使用本地回调调用.async1(),并且该回调将配置为检查队列以运行下一个排队操作(如果有)。

    这只是解决问题的一种方法。可能还有其他人。

答案 2 :(得分:0)

我建议你使用一个库,我自己创建一个库,不仅允许使用secuential链接,而且让你使用循环和ifElse结构。

https://github.com/Raising/PromiseChain

(请注意,在此示例中我们没有使用父对象,它会清理很多代码) 内部范围使用提供的名称保存每个继续的结果

var internalScope = {}; //i'll use scope

new PromiseChain(internalScope )  
    .continue(function(internalScope){ return async1();},"firstResult")  
    .continue(function(internalScope){ return async2();},"secondResult")  
    .continue(function(internalScope){ return async3();},"thridResult")  
.end();

或者,如果所有函数都属于同一个对象,并且只需要作为参数的范围,则可以执行此操作

new PromiseChain(internalScope,yourObject)  // this is important if you use the 'this' keyword inside the functions, it works as a .bind(yourObject) for every function
    .continue(yourObject.async1,"firstResult")  
    .continue(yourObject.async2,"secondResult")  
    .continue(yourObject.async3,"thridResult")  
.end();