最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

jquery - How to implement dependency between asynchronous functions in JavaScript? - Stack Overflow

programmeradmin0浏览0评论

As a simplified case, I have two async functions, foo and bar. bar needs the result of foo, i.e. bar depends on foo. I have no idea about which function will be called first.

  1. If bar is invoked first, bar will call foo and start itself right after foo is done.
  2. If foo is invoked first and done, bar can use the result of foo.
  3. If foo is invoked first and bar is invoked before foo is done, bar needs to wait for foo's result. (Don't invoke a new call to foo, just wait for the already-fired call to foo)

How can I achieve this?
Is it possible to register an async function dependency chain (something like the dependency in require.js define['foo'], function() { bar(); })?
Can I use $.deferred() to achieve it?
How?

As a simplified case, I have two async functions, foo and bar. bar needs the result of foo, i.e. bar depends on foo. I have no idea about which function will be called first.

  1. If bar is invoked first, bar will call foo and start itself right after foo is done.
  2. If foo is invoked first and done, bar can use the result of foo.
  3. If foo is invoked first and bar is invoked before foo is done, bar needs to wait for foo's result. (Don't invoke a new call to foo, just wait for the already-fired call to foo)

How can I achieve this?
Is it possible to register an async function dependency chain (something like the dependency in require.js define['foo'], function() { bar(); })?
Can I use $.deferred() to achieve it?
How?

Share Improve this question edited Aug 13, 2015 at 12:29 lzl124631x asked Aug 13, 2015 at 12:19 lzl124631xlzl124631x 4,8193 gold badges32 silver badges55 bronze badges
Add a ment  | 

5 Answers 5

Reset to default 2

In circumstances like this, the standard approach is to cache the lower level promise.

Typically you will establish, in some suitable outer scope, a js plain object as a promise cache, and always look there first before calling your async process.

var promiseCache = {};

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

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

Of course, there's nothing to prevent you also caching the higher level promise, if appropriate.

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

EDIT: forceRefresh feature

You can force a function to refresh its cached promise by passing an (extra) parameter.

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

By making forceRefresh the last argument, leaving it out is the same as passing false and foo will use the cached promise if available. Alternatively, pass true to guarantee that doSomethingAsync() be called and the cached value be refreshed.

EDIT 2: setName()/getName()

With the forceRefresh mechanism in place in getName() :

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

Alternatively, omit the forceRefresh mechanism and, assuming the cache property to be promiseCache.name :

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

The first method is more elegant, the second more efficient.

You can simply think of both functions as independent. That way, you don't go daisy-chaining dependencies that operate asynchronously. You can then have one other module that uses them.

Since they do async stuff, consider using promises. You can use jQuery's deferreds for patibility. Think of deferreds as read/write while promises are read-only.

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

});

Try creating an object property with possible values undefined , "pending" , true ; call deferred.resolve() when obj.active is true , deferred.reject() when obj.active is "pending"

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./ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<button>foo</button>
<button>bar</button>

Not sure I understood correctly the question. But here is my take at it:

  • Put you function foo into a variable

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

  • foo is async and returns something at some point. In your definition of foo, I remend that you use promises, the concept is designed to manage position of asynchronous functions in a clean and scalable way. jQuery implementation of the concept is convenient in a lot of simple use cases but suffers from some drawbacks which make it interesting for you at some point to use one of the many promises library which follow the Promises/A specification. For more information, you can refer to : Cf. https://thewayofcode.wordpress./2013/01/22/javascript-promises-and-why-jquery-implementation-is-broken/ and https://blog.domenic.me/youre-missing-the-point-of-promises

  • so, say foo takes args, and returns a promise which later resolves into some value.

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

    Here I use the RSVP promise library but any promise library following the Promises/A specification could do the job.

  • When bar is called, you can just do:

    function bar (bar_args) { var foo_promise = foo_fn.promise; // if foo was called, whether the putation 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*/}) }

NOTE : That solution is quite similar to the one proposed by Roamer-1888. One difference is that in Roamer proposal, the foo function will always return the same value after performing once its asyncronous putation. Don't know if this is the intended behaviour. In my implementation, foo executes the async. putation every time it is called. bar will use the latest puted value that is stored in the field foo_fn.promise. Older putations are lost, possible putation in progress is not taken into account.

If you are going to have this pattern often used in your code, you can also create a function working on the model of the define function in require.js.

You will need :

  • a registry to hold the dependencies functions (foo in your example)

  • the dependant function (bar in your example) will need to accept the dependencies functions puted value as part of their signature. For example, a hash of the dependencies could be passed as first parameter, so bar signature could be: {foo: foo_result}, other_bar_args...

  • the dependencies function must follow the model of my previous answer, i.e. register their promise value as a property on themselves when they execute.

  • Reminder : you need to name those dependencies functions to reference them inside their body, and then add that object to the registry.

In the define function body, you wrap the dependent function into another one which :

  • Get all dependencies from the registry

  • Get all dependencies values, executing the dependencies when necessary (similarly to my previous answer). This means you end up having a list of promises, whose results you then congregate together (RSVP.hash for example with RSVP promise library). I believe jQuery has a similar function with jQuery.when

  • you call the dependent function (bar) with this hash of results as a first argument, other arguments being the same as the wrapped function

  • that wrapped function is the new bar, so when bar is called, it will be the wrapped function which will be called.

A bit lengthy but it should work. If you want to see some code, let me know if this is what you were looking for. In any case, if you are going to have plex async. in your code, it could be interesting for you to use a pliant promise library. $.deferred is also to be used only when you have nothing better at sight as it makes it harder for you to track the behaviour of your functions : you need to keep track of all places where this deferred appears to be able to reason about your program.

发布评论

评论列表(0)

  1. 暂无评论