Strategies for handling callbacks in JS [duplicate] - javascript

This question already has answers here:
Javascript callback - how to return the result?
(3 answers)
Closed 8 years ago.
This is a noob JS question that I can't quite verbalize well enough to successfully Google.
function getUser(username){
var toReturn = { };
Restangular.one('users', username).get().then(function(result){
toReturn = result;
});
return toReturn //doesn't work
}
Restangular.one(...).get() initializes a REST call to get user data from the server. .then(...) is a callback that runs after data is returned. However, this getUser() function, as written, always returns an empty object, because it returns before the callback is triggered. How might I go about writing this function so that it returns the retrieved object?
(p.s. I know that this question is academic with regard to angular, since it handles promise resolutions transparently. I'm new to JS in general, and this is a general JS question).

Since server call is asynchronous, you should provide callback.
You can use promise or callback
Using Callback
function getUser(username, callback){
Restangular.one('users', username).get().then(function(result){
callback(result);
});
}
call: getUser('username', function(result){ /*do stuff here */ });
Using Promise
function getUser(username){
var callback;
var promise = {then: function(cb){
callback = cb;
}
};
Restangular.one('users', username).get().then(function(result){
callback(result);
});
return promise;
}
call: getUser('username').then(function(result){ /*do stuff here */ });)

Just try with:
function getUser(username, callback){
Restangular.one('users', username).get().then(callback);
}
getUser('hsz', function(result){
console.log(result);
});

The rest call is probably an async call. If you have control over the API, you can make a synchronous request which will then wait for it to return. Something like this:
function getUser(username){
var toReturn = { };
return Restangular.one('users', username).get().then(function(result){
return result;
});
}
It depends on how then is handled too. I'm assuming here that then() will return the result as well.
However, the best way in this scneario is to use a callback:
function getUser(username, callback) {
Restangular.one('users', username).get().then(callback);
}

Yes, that won't work because the problem is with your function. Every AJAX call is executed asynchronously, thus like the result.
If you have made an AJAX call like that, it will have to ask the browser to load that request, process the response and then execute the (function(result) { }) that you put as the last argument with the result.
So, you must change your function to have a callback too, like:
function getUser(username, onResultHandler){
Restangular.one('users', username).get().then(onResultHandler);
}
Then you can use it like this:
getUser('Daniel', function(user) { updateSomethingWithMyUser(user); });
Did you get it?

The simplest way, is to not overwrite the object you just created, because objects are passed around by reference.
For example:
var a = function() {
var b = {};
setTimeout(function() { b.a = 'hi'; }, 100);
return b;
}
b = a();
console.log(b); // Object {}
setTimeout(function() { console.log(b) }, 100); // Object {a: "hi"}
Because we simply set a property of the object, we are setting a property on the SAME object that got returned. When you do something like:
toReturn = result;
like in your function, you aren't changing the thing toReturn referenced, you are changing what toReturn references to (it used to reference to {}, now it references whatever result it).
So, in your case:
function getUser(username){
var toReturn = { };
Restangular.one('users', username).get().then(function(result){
toReturn.result = result;
});
return toReturn;
}
As soon as you get the result, toReturn.result will have it.

How might I go about writing this function so that it returns the retrieved object?
You can't, and you shouldn't. Restangular makes the call async so that your application can carry on running while waiting for a response.
If you want to make it look synchronous, I suggest the following approach (here's where its different from other answers):
function getUser(username){
return Restangular.one('users', username).get();
}
/* Usage */
getUser('username')
.then(function(result) {
/* do something with result */
});

Related

Chrome Extension | Is there any way to make chrome.storage.local.get() return something?

in my chrome extension I need to use chrome storage. In my background script first I create an object and add it to chrome storage and then I want to get my object from there and to be returned. Something like that:
...
var obj = {};
chrome.storage.local.set(obj, function () { });
...
var data = getData(obj); // I want my object to be returned here
var returnedData = null;
function getData(obj) {
chrome.storage.local.get(obj, function(result) {
returnedData = result; // here it works, I can do something with my object
});
return returnedData; // here it doesn't work
}
As far as I understood from here chrome.storage.local.get is asynchronous with its consequences. But is there any way how to get something from chrome storage and make it to be returned? I mean maybe I should wrap chrome.storage.local.get in another function or so?
Many thanks in advance!
If you want to stay away from global variables and you're okay with modern browser requirements, then you can implement a native JavaScript Promise object. For example, here's a function that returns the stored data for a single given key:
function getData(sKey) {
return new Promise(function(resolve, reject) {
chrome.storage.local.get(sKey, function(items) {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
reject(chrome.runtime.lastError.message);
} else {
resolve(items[sKey]);
}
});
});
}
// Sample usage given this data:
// { foo: 'bar' }
getData('foo').then(function(item) {
// Returns "bar"
console.log(item);
});
If you need support for IE11 and below, then you'll have to turn to a library like jQuery.
No it's not possible
But there are several ways around this problem
Do everything you want to do with the data returned from .get() inside the callback (or start it from there using function calls). This is what #wernersbacher posted
Take a look at deferreds (jQuery or Q libraries). A deferred's promise can be returned from getData. Inside the .get() callback, you can resolve the deferred. Outside of getData you can use .then() to do something after the deferred resolved
Something like this
function getData(obj) {
var deferred = $.Deferred();
chrome.storage.local.get(obj, function(result) {
deferred.resolve(result);
});
return deferred.promise();
}
$.when(getData(obj)).then(function(data) {
// data has value of result now
};
You have to do it like that:
var returnedData = null;
function setData(value) {
returnedData = value;
}
function getData(obj) {
chrome.storage.local.get(obj, function(result) {
setData(result); // here it works, I can do something with my object
});
return; // here it doesn't work
}
..because you tried to return a value which did not get read from storage yet, so it's null.
Update with Manifest V3 :
Now chrome.storage.local.get() function returns a promise that you can chain or can await in an async function.
const storageCache = { count: 0 };
// Asynchronously retrieve data from storage.local, then cache it.
const initStorageCache = chrome.storage.local.get().then((items) => {
// Copy the data retrieved from storage into storageCache.
Object.assign(storageCache, items);
});
Note : You must omit the callback paramter to get the promise.
Reference : https://developer.chrome.com/docs/extensions/reference/storage/#:~:text=to%20callback.-,get,-function
You need to handle it with callback functions. Here are two examples. You use a single function to set, however you create a separate function for each "On Complete". You could easily modify your callback to pass additional params all the way through to perform your needed task.
function setLocalStorage(key, val) {
var obj = {};
obj[key] = val;
chrome.storage.local.set(obj, function() {
console.log('Set: '+key+'='+obj[key]);
});
}
function getLocalStorage(key, callback) {
chrome.storage.local.get(key, function(items) {
callback(key, items[key]);
});
}
setLocalStorage('myFirstKeyName', 'My Keys Value Is FIRST!');
setLocalStorage('mySecondKeyName', 'My Keys Value Is SECOND!');
getLocalStorage('myFirstKeyName', CallbackA);
getLocalStorage('mySecondKeyName', CallbackB);
// Here are a couple example callback
// functions that get executed on the
// key/val being retrieved.
function CallbackA(key, val) {
console.log('Fired In CallbackA: '+key+'='+val);
}
function CallbackB(key, val) {
console.log('Fired In CallbackA: '+key+'='+val);
}

JavaScript - override a function with a function that contains async callbacks and still return original value

In JavaScript I want to override a function on an object, but still call the original function and return it's value. So I'd normally do something like this:
var render = res.render;
res.render = function() {
doSomethingNew();
return render.apply(this, arguments);
};
However, what if that override contains async callbacks that need to be fired first before calling the original function eg:
var render = res.render;
res.render = function() {
var self = this;
var args = arguments;
var middlewares = getSomeMiddleware();
return callNextMiddleware(middlewares, function() {
return render.apply(self, args);
});
};
function callNextMiddleware(middlewares, callback) {
var middlewareFunc = middlewares.shift();
if (middlewareFunc) {
return middlewareFunc.call(function() {
return callNextMiddleware(middlewares, callback);
});
}
else {
return callback();
}
}
Notice that I'm using a 'return' statement where required. I have one problem though, the 'middlewares' variable is an array of functions, each middleware function looks like this:
function myMiddleware(next) {
performLongRunningAsyncDataAccess(function() {
next();
});
}
Because it doesn't use 'return next()' the return value of the original res.render method is never passed back. I can get this to work if I get all the middleware functions to use 'return next()', but they come from an external source so I have no control over them, I can only guarantee that they will call 'next()'.
A bit of background, this is a Node.js app. The middleware is basically Connect middleware, and I'm trying to override the Express.js res.render method.
Generally it is a bad idea to mix asynchronous functions with return statements. Everything that you want to return, you can pass as arguments to your callback functions. So I still hope I understand your code correctly but I would assume, that you call the render function, which then grabs an array of middleware functions. Then you want to execute all the functions in that array, using the next as an callback to the previous. After all the functions have been executed, the render function should be called again, thus creating kind of an infinite loop. Assuming all of that, lets take a look at some of your return statements:
return middlewareFunc.call(function() {
return callNextMiddleware(middlewares, callback);
});
The first return in this block is useless since middlewareFunc is asynchronous and will therefore most likely return undefined. The second return statement is also useless, since it returns from the function, that you use as callback. But since your callback is just called by using next();, the return value will never be used.
else {
return callback();
}
In this block callback is the render function. So lets take a look at this function:
res.render = function() {
var self = this;
var args = arguments;
var middlewares = getSomeMiddleware();
return callNextMiddleware(middlewares, function() {
return render.apply(self, args);
});
};
So all last three return statements are essentially there, because you want to return something from your render function. But to be consistent, you should consider using a callback for that function as well:
res.render = function(callback) {
var self = this;
var args = arguments;
var middlewares = getSomeMiddleware();
callNextMiddleware(middlewares, function() {
//this will be called after all the middleware function have been executed
callback();
render.apply(self, args);
});
};
So basically you are getting rid of all the return statements and using pure asynchronous design patterns.
callNextMiddleware should return its recursive call's return value, not middlewareFunc's.
if (middlewareFunc) {
var result;
middlewareFunc.call(this, function() {
result = callNextMiddleware(middlewares, callback);
});
return result;
}
Fiddle: http://jsfiddle.net/mWGXs

Tying the result of web service call back to the calling function in Javascript

I'm probably missing something simple but given this JS code:
var WS = {
whoami: function () {
var toReturn;
$.getJSON("/SecurityData/GetCurrentUser", function (data) {
toReturn = data.Email;
});
return toReturn;
}
}
When I call it, if I put a breakpoint on the toReturn = data.Email, the expected data is there but if don't WS.whoami is undefined.
I assume this is because the $.getJSON call is async, but how can I get the desired effect?
Ajax is asynchronous and returns a promise object. Instead, return the promise object and add a callback to it.
var WS = {
whoami: function () {
return $.getJSON("/SecurityData/GetCurrentUser");
}
};
WS.whoami().done(function(data){
alert(data.Email);
});
The only other option would be to make it a synchronous request, however I do not recommend it due to the impact it will have on your UX. You would have to use $.ajax and async:false
A better solution would be to call your function with a callback. This way, your code stays async, and continues when the json call is complete.
var WS = {
whoami: function (callback) {
$.getJSON("/SecurityData/GetCurrentUser", callback);
}
}
WS.whoami(function(data) {
// code that uses var data
});

JavaScript asynchronous return value / assignment with jQuery [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 9 years ago.
I have the following jQuery Function. I'm trying to return the GUID value shown here in the alert(); The alert works fine and the value is populated, however I can't seem to assign it to a variable and return its value.
Ultimately I need to access the GUID value in other functions, etc. Everything I've tried only displays as undefined.
I'd like to do something like this:
function trackPage(){
var elqTracker = new jQuery.elq(459);
elqTracker.pageTrack({
success: function() {
elqTracker.getGUID(function(guid) {
alert(guid);
var returnValue = guid;
});
}
});
return returnValue;
}
var someGuid = trackPage();
So, this question has been asked a million times over, and I'm sure that everyone (myself included) tried this once.
It is just the nature of an asynchronous call, you can't use their results as a return value. Thats why they have you passing in a function that gets the result of the call, they can't return it either! Also notice that the elqTracker.pageTrack() function call returns IMMEDIATELY, therefore your returnValue is simply undefined.
Most people (see dfsq's answer) solve this problem by introducing a callback function as a paramater. This method is tried, and true – however jQuery has $.Deferred. This allows you to make your own asynchronous logic return a promise which you can then attach any number of callbacks to:
function trackPage(){
var elqTracker = new jQuery.elq( 459 ),
dfd = $.Deferred();
elqTracker.pageTrack({
success: function() {
elqTracker.getGUID(function( guid ) {
dfd.resolve( guid );
});
}
});
return dfd.promise();
}
// example use:
trackPage().done(function( guid ) {
alert( "Got GUID:" + guid );
});
Notice now that your trackPage() returns an object that you can attach callbacks to? You don't have to attach them immediately either.
var pageHit = trackPage().done(function( guid ) {
alert( "Page Hit GUID:" +guid );
});
$("button").click(function() {
pageHit.done( function( guid ) {
alert( "Clicked on Page GUID:" + guid );
});
});
Also, the jQuery AJAX module always returns promises as well, so the interface for all your AJAX stuff should be very similar if you make your own logic return promises.
As a side note: I'd like to point out that your var returnValue was in the wrong "scope" anyway. It needed to be declared in the outer scope of the trackPage function. Even with this fix, the concept still doesn't work.
Since you have asynchronous call the way you are trying to write code is not going to work (because by the moment of return returnValue; in the trackCode return value is not yet defined). Instead you should pass callback into trackPage:
function trackPage(callback) {
var elqTracker = new jQuery.elq(459);
elqTracker.pageTrack({
success: function() {
elqTracker.getGUID(function(guid) {
alert(guid);
// Instead of this: var returnValue = guid;
// You should use your callback function
callback(guid);
});
}
});
return returnValue;
}
trackCode(function(guid) {
// perform some actions with guid
});

Access array returned from a function - javascript/jquery noob moment

When the form is submitted, I'm calling a function getPosts and passing through a variable str. What I'd like to do is get the data returned from that function.
// when the form is submitted
$('form#getSome').submit(function(){
var str = $("form#getSome").serialize();
var something = getPosts(str);
* This is where I'd like to get the data returned from getPosts()
return false;
});
// get the data
function getPosts(str){
$.getJSON('http://myurl.com/json?'+str+'&callback=?',
function(data) {
arrPosts = new Array();
$.each(data.posts, function(i,posts){
// build array here
});
return arrPosts;
});
};
I've tried many things, but have only gotten 'undefined' returned. I've tried console.log(something);, console.log(getPosts).
I'm missing something very fundamental here. Any help would be greatly appreciated.
EDIT:
What I'm trying to do is create a single function that would get posts. Then different events would call that function. I could then use that data. So one event may be submitting a form, another may be clicking a link, another lazy/endless scrolling. All could use the same getPosts function.
There's a lot of parsing out the results which amounts to a lot of lines of code. Was just trying to find a way to reuse that function. Do you think that would be possible?
$('a.thisLink').click(function(){
getPosts();
get the return from getPosts() and do something with it
});
$('form.thisForm').submit(function(){
getPosts();
get the return from getPosts() and do something with it
});
function getPosts(){
get the posts and return an array
}
Ajax requests are executed asynchronously, the callback function (function (data)) of getJSON is executed when the request ends, and returning a value in that callback has no effect, because is a nested function inside getPosts and its return value is never used.
Actually in your example, getPosts doesn't return anything and it ends its execution before the data is returned.
I would recommend you to work on your submit event handler, if you want to keep the getPosts function, you can introduce a callback parameter:
$('form#getSome').submit(function(){
var str = $("form#getSome").serialize();
getPosts(str, function (data) {
var array = [];
$.each(data.posts, function(i,posts){
// build array here
array.push(/* value to add */);
});
// array ready to work with...
//...
});
return false;
});
function getPosts(str, callback){
$.getJSON('http://myurl.com/json?'+str+'&callback=?', callback);
}
Edit 2: In response to your second comment, you could make another callback, that will be executed when the data has been processed by the first callback, and you can define it when you execute the getPosts function on the submit event handler:
$('form#getSome').submit(function(){
var str = $("form#getSome").serialize();
getPosts(str, reusableCallback, function (result) {
// result contains the returned value of 'reusableCallback' <---
});
return false;
});
function reusableCallback(data) {
var array = [];
$.each(data.posts, function(i,posts){
array.push(/* value to add */);
});
//...
return array;
}
function getPosts(str, callback, finishCallback){
$.getJSON('http://myurl.com/json?'+str+'&callback=?', function (data) {
finishCallback(callback(data)); // pass the returned value
// of callback, to 'finishCallback' which is
// anonymously defined on the submit handler
});
}
Edit 3: I think that the getPosts function and the "reusableCallback" function are strongly related, you might want to join them, and make the code easier to use and understand:
$('form#getSome').submit(function(){
var str = $("form#getSome").serialize();
getPosts(str, function (result) {
// result contains the processed results
});
return false;
});
function getPosts(str, finishCallback){
$.getJSON('http://myurl.com/json?'+str+'&callback=?', function (data) {
// process the results here
var array = [];
$.each(data.posts, function(i,posts){
array.push(/* value to add */);
});
//...
finishCallback(array); // when the array is ready, execute the callback
});
}
Your getPosts function looks incomplete, I'm no jquery expert but should it look something like:
function getPosts(str) {
$.getJSON('http://myexample.com/json?'+str+'&callback=?',function(data){
var arrPosts = [];
$.each(data.posts, function(i,posts){
... build array yada yada ...
});
return arrPosts;
});
}
The problem is that the $.getJSON callback function gets called when the get request returns the data, not inline with your function.

Categories

Resources