Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid this async lazy pattern?

Many times, I needed to write such a lazy asynchronous loading in Javascript:

if (myvar != undefined){
    doSomeTreatment(myvar)
} else {
    loadMyVarAsynchronously().then(function(value){
        myvar = value
        doSomeTreatment(myvar)
    })
}

Here, myvar would be some attribute of a hash, not a local variable. loadMyVarAsynchronously loads asynchronously the value for myvar (with, for example, a Promise or a JQuery Deferred)

Is there a pattern to avoid having to write twice the following line in this code?

doSomeTreatment(myvar)
like image 640
Rémi Doolaeghe Avatar asked Jan 28 '15 14:01

Rémi Doolaeghe


3 Answers

If myvar is already defined you can pass that into $.when() and have it automatically wrapped into a resolved promise:

var def = (myVar !== undefined) ? myVar : loadMyVarAsynchronously();

$.when(def).then(doSomeTreatment);

Make the assignment to myVar inside doSomeTreatment(), if required, instead of inside the anonymous .then callback, this allows you to pass doSomeTreatment directly as a function reference.

EDIT oops, did you want standard Promises, or jQuery promises? The above is jQuery style.

like image 67
Alnitak Avatar answered Oct 06 '22 00:10

Alnitak


It's really hard to reason about what you are doing here without a concrete, real-world example. This is definitely not a pattern I use a lot in my code so I have a suspicion that you could go around fixing it by re-assessing your assumptions.

That being said, you can use promise chaining. Note in this case promiseThatLoadsVar IS a promise, it's not a function that returns a promise. That means it maintains a state, and the second time this code runs onward it will return immediately.

promisethatLoadsVar.then(function(value) {
     myvar = value;
     doSomeTreatment(myvar);
})

If you could give a clearer question I'd love to provide a clearer answer for you.

Edit: I want to elaborate on the example posed in the comments. Here is a solution using the LoDash library. (BTW, you should use lodash or underscore, they are basically the standard library of javascript).

var getData = _.once(function() { return $.getJSON(...) });

$('button').on('click', function() { 
    getData().then(function(data) { showDialog(data) }) 
})

Note here that getData is a wrapped function that after the first invocation will just return the same thing over and over without re-invoking the function. The first time you invoke it, it will return a promise that resolves when data is retrieved. The second time onward you get back the same promise which will likely already be resolved.

Do you want to pass in parameters to getData?

var getData = _.memoize(function(id) { return $.getJSON(url+id) });

$('button').on('click', function() { 
    getData($('#selector').val()).then(function(data) { showDialog(data) }) 
})

This will do the same thing, but cache the promises by the first input parameter passed to getData.

like image 42
George Mauer Avatar answered Oct 06 '22 02:10

George Mauer


Yes, you should build a promise for the value. Then you just need to attach that function only once as a handler:

var loadedMyVar = myvar!=undefined ? Promise.resolve(myvar) : loadMyVarAsynchronously();
loadedMyVar.then(doSomeTreatment);

Oh, and you shouldn't need to reassign to myvar asynchronously, just use the loadedMyVar promise everywhere. Maybe even use

var loadedMyVar = loadedMyVar || loadMyVarAsynchronously();
loadedMyVar.then(function(value) {
    return myvar = value;
}).then(doSomeTreatment);
like image 23
Bergi Avatar answered Oct 06 '22 01:10

Bergi