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

How to implement dependency between asynchronous functions in JavaScript?

本文关键字:之间 函数 依赖 关系 异步 实现 JavaScript      更新时间:2023-09-26

作为一个简化的情况,我有两个异步函数,foobarbar需要foo的结果,即 bar取决于foo.我不知道将首先调用哪个函数。

  1. 如果首先调用barbar将调用foo并在foo完成后立即启动自身。
  2. 如果首先调用并完成foo,则bar可以使用 foo 的结果。
  3. 如果先调用foobarfoo完成之前调用,bar需要等待foo的结果。(不要调用新的调用foo,只需等待已经触发的调用foo(

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

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

通常,您将在某个合适的外部作用域中建立一个 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.
});

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

您可以简单地将这两个函数视为独立的。这样,您就不会以菊花链方式连接异步运行的依赖项。然后,您可以拥有另一个使用它们的模块。

由于他们做异步的事情,请考虑使用承诺。你可以使用 jQuery 的延迟来实现兼容性。将延迟视为读/写,而承诺是只读的。

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

尝试使用可能的值创建对象属性 undefined"pending"true ;当obj.active true时调用 deferred.resolve()deferred.reject()obj.active"挂起"时调用

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 是异步的,并在某个时候返回一些东西。在你对foo的定义中,我建议你使用promises,这个概念旨在以干净和可扩展的方式管理异步函数的组合。jQuery概念的实现在许多简单的用例中都很方便,但存在一些缺点,这使得您在某个时候使用遵循Promises/A规范的众多Promise库之一变得有趣。有关更多信息,您可以参考:参看 https://thewayofcode.wordpress.com/2013/01/22/javascript-promises-and-why-jquery-implementation-is-broken/和 https://blog.domenic.me/youre-missing-the-point-of-promises

  • 因此,假设 Foo 接受 args,并返回一个承诺,该承诺稍后解析为某个值。

    var foo_fn = function foo(foo_args) { return foo_fn.promise = new RSVP.Promise (resolve, reject) {
    // Your async code goes here } }

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

  • 当调用 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 函数在执行一次异步计算后将始终返回相同的值。不知道这是否是预期的行为。在我的实现中,foo执行异步。每次调用时计算。 bar将使用存储在字段 foo_fn.promise 中的最新计算值。较旧的计算将丢失,不考虑正在进行的可能计算。

如果你打算在代码中经常使用此模式,你也可以创建一个处理define模型的函数 功能在require.js .

您将需要 :

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

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

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

  • 提醒 :您需要命名这些依赖项函数以在其正文中引用它们,然后将该对象添加到注册表中。

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

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

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

  • 你调用依赖函数(bar(作为第一个参数,这个结果哈希作为第一个参数,其他参数与包装的函数相同

  • 该包装函数是新的bar,因此当调用bar时,将被调用的是包装的函数。

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