This question already has answers here:
How do you find out the caller function in JavaScript?
(36 answers)
Closed 5 years ago.
Is there a way in vanilla Javascript (ES5) to get the caller function and re execute it after an async call is done without the need to pass it as a callback function?
I am building a caching mechanism on a system and for some reason it's not possible for me to use promises, es6's generator function etc (any modern js features which I thought could be of help).
Right now, I'm coding it this way (this is a much simplified version):
var cache = {};
var cacheCallbackQueue = [];
var theCallerFunction = function(someParam){
loadCache("aDataTarget",function(){
theCallerFunction(someParam);
});
// cache will be used here somehow
// if the needed cache haven't been loaded
// the async cacher should re-execute this caller function after the caching is complete
}
var anotherCallerFunction = function(anotherParam){
loadCache("anotherDataTarget",function(){
anotherCallerFunction(anotherParam);
});
}
var loadCache = function(cacheId,callback){
asyncCall(cacheId, function(result){
cache[cacheId] = result;
cacheCallbackQueue.push(callback);
// is there a way to get the caller function automatically
// without the need to pass it as callback function on the parameter
checkCachingStatus();
})
}
// check if caching is completed,
// if the caching is completed,
// it will execute all previously queued callback function
var checkCachingStatus = function(){
var cachingStatus = "complete";
if(!cache["aDataTarget"] || !cache["anotherDataTarget"]){
cachingStatus = "incomplete";
}
if(cachingStatus === "complete"){
for(var key in cacheCallbackQueue){
cacheCallbackQueue[key]();
}
}
};
theCallerFunction("whatever");
anotherCallerFunction(666);
I am not sure if I'm coding it 'the right javascript way', I'm open for another suggestion
Is there a way in vanilla Javascript (ES2015) to get the caller function and re execute it after an async call is done without the need to pass it as a callback function?
Not in standard JavaScript, no. There was a nonstandard extension some JavaScript engines added, caller, but it is not standard and is forbidden in strict mode.
I am not sure if I'm coding it 'the right javascript way', I'm open for another suggestion
There are a couple of "right JavaScript ways":
Pass the function into loadCache, which you've said you don't want to do (but which you're doing)
Have loadCache return an object that provides a means of subcribing to events, and have a "retry" event that you can subscribe to; but subscribing to an event means passing a handler function in, which...you've said you don't want to do :-)
Having said all that, it's unclear why loadCache would need to re-call the function that called it. The standard way of handling this would be to use promises (which you can use in ES5 with a polyfill; the polyfill isn't even that big): loadCache would return a promise and then the code calling it would use it:
var theCallerFunction = function(someParam) {
loadCache(/*...*/)
.then(function(data) {
// Use `data` here
})
.catch(function() {
// Handle failure here
});
// Probably no code here, since you want to wait for the cached information
};
or if the caller should handle errors:
var theCallerFunction = function(someParam) {
return loadCache(/*...*/)
.then(function(data) {
// Use `data` here
});
};
Related
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 8 months ago.
i have the following code.
i'm trying to use the variables rsname,rslevel and rsexp outside of the .then statement but i can't seem to find a way to get this done. (the module im using can be found here: https://pqt.gitbook.io/runescape-api).
const { hiscores } = require('runescape-api');
var rsn = "le me";
var rsnlookup = hiscores.getPlayer(rsn).then(data => {
var rsname = data.name;
var rslevel = data.skills.dungeoneering.level;
var rsexp = data.skills.dungeoneering.experience;
console.log(rsname);
console.log(rslevel);
console.log(rsexp);
})
rsnlookup.catch((error) => {
console.error("This username doesn\'t exist.");
});
console.log(rsnlookup);
You can't, with your current setup. This is the way it's meant to be. Whatever you do with those variables can't leave that function.
You can pass the data somewhere else though using a function like this:
const { hiscores } = require('runescape-api');
var rsn = "le me";
function doSomethingWithData(rsname, rslevel, rsexp) {
console.log(rsname, rslevel, rsexp);
}
var rsnlookup = hiscores.getPlayer(rsn).then(data => {
var rsname = data.name;
var rslevel = data.skills.dungeoneering.level;
var rsexp = data.skills.dungeoneering.experience;
doSomethingWithData(rsname, rslevel, rsexp);
})
rsnlookup.catch((error) => {
console.error("This username doesn\'t exist.");
});
This is how JavaScript is supposed to work. Whenever you make a call out to another server, JS does not stop and wait for the request to come back. It actually continues executing onward while the request is still traveling across the internet to you. This is why you need to provide a callback function for it to come back and execute when it's done.
So for example, here's a simpler breakdown:
// The variables must be declared outside the function for them
// to be accessible from outside the function.
let rsname;
let rslevel;
let resexp;
// This call only BEGINS the request. It will not stay and wait for it.
hiscores.getPlayer(rsn).then(data => {
// This code is only executed after the request comes back with the data,
// so the data can only be used from here.
// These variables do not get declared until this time,
// so they won't exist outside this function until the request is done.
// You need to pass them out to wherever they are needed from here,
// or do all your logic with it right here.
rsname = data.name;
rslevel = data.skills.dungeoneering.level;
rsexp = data.skills.dungeoneering.experience;
});
// This is executed the moment the request begins, it does not wait for it to return.
console.log(rsname); // The variables are still undefined here since the request is not back yet here.
Essentially, all "asynchronous" means is that the code will BEGIN executing, and continue onward without waiting for it to finish. Then when it's done it comes back to execute some kind of callback function.
I want to implement a dynamic loading of a static resource in AngularJS using Promises. The problem: I have couple components on page which might (or not, depends which are displayed, thus dynamic) need to get a static resource from the server. Once loaded, it can be cached for the whole application life.
I have implemented this mechanism, but I'm new to Angular and Promises, and I want to make sure if this is a right solution \ approach.
var data = null;
var deferredLoadData = null;
function loadDataPromise() {
if (deferredLoadData !== null)
return deferredLoadData.promise;
deferredLoadData = $q.defer();
$http.get("data.json").then(function (res) {
data = res.data;
return deferredLoadData.resolve();
}, function (res) {
return deferredLoadData.reject();
});
return deferredLoadData.promise;
}
So, only one request is made, and all next calls to loadDataPromise() get back the first made promise. It seems to work for request that in the progress or one that already finished some time ago.
But is it a good solution to cache Promises?
Is this the right approach?
Yes. The use of memoisation on functions that return promises a common technique to avoid the repeated execution of asynchronous (and usually expensive) tasks. The promise makes the caching easy because one does not need to distinguish between ongoing and finished operations, they're both represented as (the same) promise for the result value.
Is this the right solution?
No. That global data variable and the resolution with undefined is not how promises are intended to work. Instead, fulfill the promise with the result data! It also makes coding a lot easier:
var dataPromise = null;
function getData() {
if (dataPromise == null)
dataPromise = $http.get("data.json").then(function (res) {
return res.data;
});
return dataPromise;
}
Then, instead of loadDataPromise().then(function() { /* use global */ data }) it is simply getData().then(function(data) { … }).
To further improve the pattern, you might want to hide dataPromise in a closure scope, and notice that you will need a lookup for different promises when getData takes a parameter (like the url).
For this task I created service called defer-cache-service which removes all this boiler plate code. It writted in Typescript, but you can grab compiled js file. Github source code.
Example:
function loadCached() {
return deferCacheService.getDeferred('cacke.key1', function () {
return $http.get("data.json");
});
}
and consume
loadCached().then(function(data) {
//...
});
One important thing to notice that if let's say two or more parts calling the the same loadDataPromise and at the same time, you must add this check
if (defer && defer.promise.$$state.status === 0) {
return defer.promise;
}
otherwise you will be doing duplicate calls to backend.
This design design pattern will cache whatever is returned the first time it runs , and return the cached thing every time it's called again.
const asyncTask = (cache => {
return function(){
// when called first time, put the promise in the "cache" variable
if( !cache ){
cache = new Promise(function(resolve, reject){
setTimeout(() => {
resolve('foo');
}, 2000);
});
}
return cache;
}
})();
asyncTask().then(console.log);
asyncTask().then(console.log);
Explanation:
Simply wrap your function with another self-invoking function which returns a function (your original async function), and the purpose of wrapper function is to provide encapsulating scope for a local variable cache, so that local variable is only accessible within the returned function of the wrapper function and has the exact same value every time asyncTask is called (other than the very first time)
I'm having an issue that is burning my head. Basically this is the scenario:
I have a callController() function, which just simple use the jQuery.load() method to load a controller, after loaded, I can play with the returning parameters.
Now... in one of my controllers there is a validation rule I need to check in order to to allow the user to execute certain functionality, however, I create a function like this:
function myValRule() {
var val = callController('blablabla'),
response = "";
val.done(function(data) {
//my business logic
response = something;
}
return response;
}
As you imagine, response, even if It has a value, it returns undefined, so I set a timeout and console.log() it and now it has a value, but I cannot make to return this value even if I put the return into the setTimeout(). So basically when the method call this function to validate it finds it empty.
Can you point me on some direction to solve this issue?
Remember this is asynchronous! The return response; is long gone by the time .done() is actually called.
if you need the value returned in this fashion, look at either supplying a callback function in your myValRule function (that would be called from within .done()) or maybe look at using the $.Deferred api so you can mimic what callController(...) is doing.
Here's an example of both scenarios (with example call to myValRule):
Callback argument
function myValRule(callback){
var val = callController('blablabla'),
val.done(function(data){
var response = /* something */;
callback(response);
});
}
myValRule(function(response){
// here you have response
});
Using $.Deferred
(I assume it's jQuery since there's a .done call)
function myValRule(){
var df = $.Deferred(),
val = callController('blablabla');
val.done(function(data){
var response = /*something */;
df.resolve(response);
}).fail(df.reject);
return df.promise();
}
myValRule().done(function(response){
// here you have response
});
Kept on seeing this pattern in code, but couldn't find any reference to it in google or SO, strange. Can someone point me to reference for this.async() function?
var done = this.async();
// ...
$.get(path, function(contents) { // or some other function with callback
// ...
done(JST[path] = tmpl);
})
var done = this.async() and done(blah) is a clever trick to return a value fetched from asynchronous call (e.g. $.get) within a synchronous function.
Let's see an example:
var getText = function() {
return "hello";
};
var text = getText();
It's a pretty straightforward function call so no puzzle here. However, what if you need to fetch the text asynchronously in getText() function?
var getText = function() {
return $.get('<some-url>', function(text) {
return text;
}); // ??????
};
call to getText() doesn't return the text you want to get. It returns jquery's promise object.
So how do we make getText() return the text it gets from $.get() call?
var getText = function() {
var done = this.async();
$.get('<some-url>', function(text) {
done(text);
});
};
var text = getText(); // you get the expected text
Magic, right?
I don't know the inner-working of this.async() call yet. I don't know if there is a library provides that function, but you can see that Backbone.LayoutManager uses this trick https://github.com/tbranyen/backbone.layoutmanager/blob/master/backbone.layoutmanager.js (search for this.async).
Also, Tim Branyen (the author of backbone layoutmanager) briefly talks about it in his video tutorial (http://vimeo.com/32765088 around 14:00 - 15:00). In the video, Tim says Ben Alman came up with that trick. Take a look at this as well https://github.com/cowboy/javascript-sync-async-foreach
I think it's a pretty neat trick to mix async and sync functions.
Cheers,
var done = this.async() is a pattern used in Grunt to help perform asynchronous functions within a Task.
You need to invoke done() or done(returnValues) to tell Grunt the task is complete (after your chain of asynchronous tasks).
Read more about it:
https://gruntjs.com/inside-tasks#inside-all-tasks
It is a way to work around the problem of this escaping inside callback. Without this extra reference the code would look like this:
$.get(path, function(contents) { // or some other function with callback
//Wrong! `this` might no longer point to your object
this.done(JST[path] = tmpl);
})
Unfortunately! this inside response callback is not the same as this outside of it. In fact it can be anything, depending on what $.get (calling the callback using) decides it to be. Most of the people use extra reference named that for the same purpose:
var that = this;
// ...
$.get(path, function(contents) { // or some other function with callback
// ...
that.async(JST[path] = tmpl);
})
This pattern also seems reasonable and readable.
Oh, and if you are curious about this syntax:
done(JST[path] = tmpl)
This is an assignment used as an expression. The value of assignment is the right-hand side, so this code is equivalent to:
JST[path] = tmpl;
done(tmpl);
Because of the complexity of this application, I have a need to wrap Facebook API calls, like so.
//In main file, read is always undefined
var read = fb_connect.readStream();
// In fb_wrapper.js
function readStream () {
var stream;
FB.api('/me/feed', {limit:10000}, function (response) {
stream = response.data;
});
return stream;
}
I know that due to the asynchronous nature of the call, the rest of the readStream() function will return stream (which has no value). I am having trouble finding a way of getting the data out of the callback function scope and back up to a higher scope. The FB API call is returning fine (I have debugged it a hundred times), but getting that response data has been the battle thus far.
If anyone has any suggestions, it would be much appreciated. I searched for Facebook jQuery plug-ins (as a pre-made wrapper, perhaps) with little luck.
Judging from your question, it seems that you are looking for a synchronous call. Which means that you'd want to use the data returned from the api call right after calling it. In that case, you'll need to check whether FB.api supports synchronous calls (mostly doesn't).
Otherwise, you'll need to understand that you are making an async call here. Which means that you should put your handling code INSIDE the callback function that you pass to FB.api. This is called the "continuation" style of writing code and is the standard way to use async calls.
FB.api('/me/feed', {limit:10000}, function (response) {
var stream = response.data;
// Do your processing here, not outside!!!
});
Or:
function handlerFunction(response) {
// Do your processing here
}
FB.api('/me/feed', {limit:10000}, handlerFunction);