Reputation: 787
I am trying to wrap my head around how to make asynchronous programming work.
In my current use-case, I have functions that get called potentially multiple times per second and they have callbacks that rely on multiple variables that could change in between them.
A simplified example: (using coffeescript for brevity)
doSomething = (requestor, thing, action, callback) ->
thing.takeAction action, (result) ->
# actually a lot of times this nests down even further
requestor.report result
callback result
If doSomething gets called multiple times with different data before thing.takeAction returns its result, I assume I can't rely on requestor and callback still being the same things I need them to be. Correct?
To circumvent this I'd need to somehow inject requestor and callback into takeAction's callback. Is that possible somehow?
I got the idea of doing something like
doSomething = (requestor, thing, action, callback) ->
thing.takeAction action, (result, _requestor = requestor, _callback = callback) ->
_requestor.report result
_callback result
But that of course is just a CoffeeScript hack and doesn't work at all.
By the way, I was trying to use the caolan/async module to help me with this, but the fact still remains that I often need more variables in the callbacks than async lets me provide. Like:
doSomething = function(requestor, thing, action, callback) {
// this might not need a waterfall, but imagine it would have nested further
async.waterfall(
[
function(next) {
thing.takeAction(action, function(result) {
// How can I know that action is still the same?
next(null, result);
});
},
function(result, next) {
requestor.report(result); // requestor still the same?
next(null, result);
}
],
function(err, result) {
callback(result); // callback still the same?
});
}
It still leaves me with the same problem. So how do I do this?
Thank you for your time.
Upvotes: 1
Views: 1162
Reputation: 32840
I'm not sure whether using CS really helped this example, let me put it with plain JS:
var doSomething = function (requestor, thing, action, callback) {
thing.takeAction(action, function (result) {
requestor.report(result);
callback(result);
});
};
// Following is completely safe:
doSomething(r1, t1, a1, c1);
doSomething(r2, t2, a2, c2);
doSomething(r3, t3, a3, c3);
Each time doSomething is invoked, new function scope is created. So internally, function that is created and passed to takeAction, has access to arguments with which originally doSomething was invoked (another call to doSomething doesn't change that!). That's the way scoping in JavaScript works
Upvotes: 1
Reputation: 9912
You need to separate the action
object content from the action
value itself. That is, there is some object in the memory, which is referenced by the action
name in this specific context.
For example,
function test(action) {
alert("Test1: " + action.value);
setTimeout(function () { alert("Test2: " + action.value); }, 1000);
}
var action = { value: 1; };
test(action);
action = { value: 2 };
alert("Action value outside: " + action.value);
will alert "Test1: 1", "Action value outside: 2" and "Test1: 1". However, once you replace action = { value: 2 };
with action.value = 2
, the last alert will change to "Test1: 2".
So, if your problem is that some fields of the action object are changed outside, just clone it in the first line of your doSomething. If your problem is that the reference to the object is changed outside, you should not worry, it won't affect your doSomething
in any way.
Additionally, the subsequent calls to doSomething do not "override" the value of the action parameter in your callbacks, as it is closed over the specific call: en.wikipedia.org/wiki/Closure_(computer_science)
For example,
function test(action) {
alert("Test1: " + action.value);
setTimeout(function () { alert("Test2: " + action.value); }, 1000);
}
var action = { value: 1; };
test(action);
action = { value: 2 };
test(action);
will alert "Test1: 1", "Test1: 2", "Test2: 1" and "Test2: 2" (not "Test1: 1", "Test1: 2", "Test2: 2" and "Test2: 2" as you seem to fear).
Upvotes: 2