如何在JavaScript中实现异步函数之间的依赖?

How to implement dependency between asynchronous functions in JavaScript?

作为一个简化的案例,我有两个异步函数,foobarbar需要foo的结果,即bar依赖于foo。我不知道哪个函数会先被调用。

  1. 如果先调用 barbar 将调用 foo 并在 foo 完成后立即开始。
  2. 如果foo先调用完成,bar可以使用foo的结果。
  3. 如果foo先调用,barfoo完成之前调用,bar需要等待foo的结果。 (不要调用对 foo 的新调用,只需等待对 foo 的已触发调用)

我怎样才能做到这一点?
是否可以注册异步函数依赖链(类似于 require.js define['foo'], function() { bar(); } 中的依赖)?
我可以用$.deferred()来实现吗?
如何?

你可以简单地认为这两个功能是独立的。这样,您就不会菊花链式地连接异步操作的依赖项。然后你可以有另一个模块使用它们。

由于他们执行异步操作,请考虑使用 promises。您可以使用 jQuery 的 deferreds 来实现兼容性。将延迟视为 read/write 而承诺是只读的。

// foo.js
define(function(){
  return function(){
    return new Promise(function(resolve, reject){
      // Do async stuff. Call resolve/reject accordingly
    });
  };
});

// bar.js
define(function(){
  return function(){
    return new Promise(function(resolve, reject){
      // Do async stuff. Call resolve/reject accordingly
    });
  };
});

// Your code (Excuse the CommonJS format. Personal preference)
define(function(require){

  // Require both functions
  var foo = require('foo');
  var bar = require('bar');

  // Use them
  foo(...).then(function(response){
    return bar();
  }).then(function(){
    // all done
  });;

});

在这种情况下,标准方法是缓存较低级别的承诺。

通常你会在一些合适的外部范围内建立一个 js 普通对象作为 promise 缓存,并且在调用你的异步进程之前总是先在那里查找。

var promiseCache = {};

function foo() {
    if(!promiseCache.foo) {
        promiseCache.foo = doSomethingAsync();
    }
    return promiseCache.foo;
}

function bar() {
    return foo().then(doSomethingElseAsync);
}

当然,如果合适的话,没有什么可以阻止您缓存更高级别的承诺。

function bar() {
    if(!promiseCache.bar) {
        promiseCache.bar = foo().then(doSomethingElseAsync);
    }
    return promiseCache.bar;
}

编辑:forceRefresh 特征

您可以通过传递(额外)参数强制函数刷新其缓存的承诺。

function foo(any, number, of, other, arguments, forceRefresh) {
    if(forceRefresh || !promiseCache.foo) {
        promiseCache.foo = doSomethingAsync();
    }
    return promiseCache.foo;
}

通过将 forceRefresh 作为最后一个参数,将其省略与传递 false 相同,并且 foo 将使用缓存的承诺(如果可用)。或者,传递 true 以保证调用 doSomethingAsync() 并刷新缓存值。

编辑 2:setName()/getName()

getName() 中使用 forceRefresh 机制:

setName(newName).then(getName.bind(null, true)); //set new name then read it back using forceRefresh.

或者,省略 forceRefresh 机制,假设缓存 属性 为 promiseCache.name :

setName(newName).then(function() {
    promiseCache.name = $.when(newName);//update the cache with a simulated `getName()` promise.
});

第一种方法更优雅,第二种方法更高效。

尝试使用可能的值 undefined"pending"true 创建一个对象 属性 ;当 obj.activetrue 时调用 deferred.resolve() ,当 obj.active 为 "pending"

时调用 deferred.reject()

var res = {
  active: void 0
};

var foo = function foo(state) {
  var t;
  var deferred = function(type) {
    return $.Deferred(function(dfd) {
        
        if (res.active === "pending" || state && state === "pending") {
          res.active = "pending";
          dfd.rejectWith(res, [res.active])
        } else {
          res.active = state || "pending";
          t = setInterval(function() {
            console.log(res.active)
          }, 100);
            setTimeout(function() {
              clearInterval(t)
              res.active = true;
              dfd.resolveWith(res, [res.active])
            }, 3000);
        }
        return dfd.promise()
      })
      .then(function(state) {
        console.log("foo value", state);
        return state
      }, function(err) {
        console.log("foo status", err)
        return err
      })
  }
  return deferred()
}

var bar = function bar(result) {
  var deferred = function(type) {
    return $.Deferred(function(dfd) {
      if (result && result === true) {
        setTimeout(function() {
          dfd.resolveWith(result, [true])
        }, 1500)
      } else {
        dfd.rejectWith(res, [res.active || "pending"])
      };
      return dfd.promise()
    })
  }
  return deferred().then(function(data) {
    console.log("bar value", data);
  }, function(err) {
    console.log("bar status", err);
  })
}

$("button").click(function() {
  $(this).is(":first") 
  ? foo().then(bar, bar) 
  : bar(res.active === true ? res.active : "pending")
    .then(foo, foo).then(bar, bar)
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<button>foo</button>
<button>bar</button>

不确定我是否正确理解了问题。但这是我的看法:

  • 将函数 foo 放入变量中

    var foo_fn = function foo(foo_args){// Your async code goes here}

  • foo 是异步的,returns 在某些时候是异步的。在您对 foo 的定义中,我建议您使用 promises,该概念旨在以干净且可扩展的方式管理异步函数的组合。 jQuery 这个概念的实现在很多简单的用例中都很方便,但也有一些缺点,这让你在某些时候对使用遵循 Promises/A 规范的众多 promises 库之一很感兴趣。更多信息,您可以参考: 比照。 https://thewayofcode.wordpress.com/2013/01/22/javascript-promises-and-why-jquery-implementation-is-broken/ and https://blog.domenic.me/youre-missing-the-point-of-promises

  • 所以,假设 foo 接受 args,returns 是一个承诺,后来解析为一些值。

    var foo_fn = function foo(foo_args) { return foo_fn.promise = new RSVP.Promise(解决,拒绝){<br> // 你的异步代码在这里 } }

    这里我使用 RSVP promise 库,但任何遵循 Promises/A 规范的 promise 库都可以完成这项工作。

  • 当bar被调用时,你可以这样做:

    function bar (bar_args) { var foo_promise = foo_fn.promise; // if foo was called, whether the computation is in progress or finished, // the foo_fn.promise field will be non-empty, as foo returns immediately // with a promise anytime it is called `` if (!foo.promise) { // foo has not yet been called so call it foo_promise = foo(foo_args); } foo_promise.then (function (foo_result) {/*some async code here*/}) }

注意:该解决方案与 Roamer-1888 提出的解决方案非常相似。一个区别是,在 Roamer 提案中,foo 函数在执行一次异步计算后将始终 return 相同的值。不知道这是否是预期的行为。在我的实现中,foo 执行异步。每次调用时计算。 bar 将使用存储在字段 foo_fn.promise 中的最新计算值。较早的计算已丢失,可能正在进行的计算未被考虑在内。

如果您打算在您的代码中经常使用此模式,您还可以创建一个在 define 模型上工作的函数 在 require.js 中发挥作用。

您将需要:

  • 用于保存依赖项函数的注册表(在您的示例中为foo

  • 依赖函数(在您的示例中为 bar)将需要接受依赖函数计算值作为其签名的一部分。例如,依赖项的散列可以作为第一个参数传递,因此 bar 签名可以是:{foo: foo_result}, other_bar_args...

  • 依赖函数必须遵循我之前回答的模型,即在执行时将它们的承诺值注册为属性。

  • 提醒:您需要命名那些依赖函数以在其主体内引用它们,然后将该对象添加到注册表。

define 函数体中,将依赖函数包装到另一个函数中:

  • 从注册表获取所有依赖项

  • 获取所有依赖项值,必要时执行依赖项(类似于我之前的回答)。这意味着您最终会得到一个承诺列表,然后将其结果聚集在一起(RSVP.hash 例如使用 RSVP 承诺库)。我相信 jQuery 与 jQuery.when

  • 具有相似的功能
  • 您调用依赖函数 (bar) 并将此结果哈希作为第一个参数,其他参数与包装函数相同

  • 包装函数是新的bar,所以当调用bar时,将调用包装函数。

有点长,但应该可以。如果您想查看一些代码,请告诉我这是否是您要找的。无论如何,如果你要有复杂的异步。在您的代码中,使用兼容的 promise 库对您来说可能会很有趣。 $.deferred 也只能在你看不到更好的东西时使用,因为它会让你更难跟踪你的函数的行为:你需要跟踪这个 deferred 似乎能够推理你的所有地方程序。