I'm relatively new to JavaScript and repeatedly find myself writing methods in a helper object which take in a callback as a parameter e.g.
var utilities = {
getTweets: function (user, maxTweets, callBack) {
var obj = $(this);
$.getJSON('http://api.twitter.com/1/statuses/user_timeline.json?callback=?&screen_name=' + user + "&count=" + maxTweets, function (data) {
callBack(data);
});
};
I then call it like so:
utilities.getTweets("TESTUSER", 4, function (tweets) {
.....
});
Given I am calling the code above using setInterval is this likely to leak over time/is there a better way to write this?
What you're doing is mostly fine, except that there's no need to create the extra closure. Passing a closure written like:
function (data) {
callBack(data);
}
is just the same as passing callBack directly in the parameter list.
However if you can guarantee running with jQuery 1.5 or later, then a better method is to just have getTweets() return the JQXHR object, and then you can use "deferred" methods in the client code:
var utilities = {
getTweets: function (user, maxTweets) {
var uri = 'http://api.twitter.com/1/statuses/user_timeline.json?callback=?';
var data = {
screen_name: user,
count: maxTweets
};
return $.getJSON(uri, data);
});
};
and then in the client code:
utilities.getTweets(user, maxTweets).done( /* your callback here */ );
In this way you can completely decouple the callback from the implementation. Indeed you can register multiple callbacks, and error handlers, all without touching the implementation of utilities.
NB: use of a map for data above also protects your code against parameter injection.
If you're afraid of the memory overhead of creating that function every time, then do something like:
utilities.getTweets("TESTUSER", 4, utilities.handleTweets);
And in utilities.handleTweets you do as you do in the callback.
You could use something like this:
var utilities = {
options: {
user: 'value',
maxTweets: '4'
}
getTweets: function() {
// access a value
this.options.user;
}
}
Or it would be best to create a proper plugin with options etc. Have a read of this article, it should be exactly what you need:
http://jquery-howto.blogspot.com/2009/01/how-to-set-default-settings-in-your.html
Related
I set up a callback function inside my Meteor async method to be called on "readable" event. But the callback is not being called when the on."readable" is being fired (I know it's being fired from the console.log I set up).
Am I missing something here? I've been at it for a few hours now trying a few different things!
Meteor.startup(() => {
Meteor.call("getfeed", function(feedloader) {
//I get: TypeError: undefined is not a function]
console.log(feedloader);
});
});
Meteor.methods({
getfeed: function(callb) {
var req = request('http://feeds.feedburner.com/Techcrunch');
var feedparser = new FeedParser();
testing = [];
//........a bunch of functions........
feedparser.on('readable', function() {
var stream = this
, meta = this.meta
, item;
while (item = stream.read())
{
//I'm pushing the results into testing var
testing.push(item);
}
//From the logs I can see that this is called 12 times
//but the callback's not firing!!!
console.log(testing.length);
callb(testing);
});
}
});
Meteor methods are not asynchronous functions in the sense that they do not get the callback argument even though you pass it when you "call" a method. Instead each method is executed within a Fiber which is another flavor of dealing with asynchronous code.
Fortunately, Meteor has a nice helper that allows you to mix both styles. What you need to do is wrap the "pure" asynchronous part of your method code with Meteor.wrapAsync. This structure should look more or less like this:
Meteor.methods({
getfeed: function() {
var wrapped = Meteor.wrapAsync(function (callb) {
var feedparser = new FeedParser();
testing = [];
// ...
feedparser.on('readable', function() {
// probably the same code you have, but without "callb()"
});
feedparser.on('end', function () {
// NOTE: No error here, so the first argument must be null.
callb(null, testing);
})
});
// NOTE: Finally, call the wrapped function
return wrapped();
}
});
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);
}
I'm trying to solve a JavaScript challenge where I have to use asynchronous callbacks.
Here is what the challenge says:
Define a function named doStuffAsync that takes one argument callback. Your function should read the contents of file "passwords", write the result to file "world.txt" along with the extra text "OWNED", then call callback with no arguments. Use both the asynchronous readAsync and asynchronous writeAsync.
My code is as follows:
var files = { "passwords": "abc,def",
"world.txt": "hello" };
var readAsync = function(file, callback) {
callback(files[file]);
};
var writeAsync = function (file, contents, callback) {
files[file] = contents;
callback();
};
var test = function() {
files["out.txt"] = "final";
files["passwords"] = "blank";
};
//this is the part I'm interested in
var doStuffAsync = function (callback) {
var contents = 0;
contents = readAsync("passwords");
writeAsync("world.txt", contents, callback());
};
Here is the link to the challenge http://nathansjslessons.appspot.com/lesson?id=1085
The most straightforward way to accomplish this using just callbacks is by nesting the function calls like this:
function doStuffAsync(callback) {
readAsync("passwords", function(contents) {
writeAsync("world.txt", contents + "OWNED", function() {
callback();
});
});
}
In this case, since your callback function doesn't require any arguments, you can save one level of nesting and just do this:
function doStuffAsync(callback) {
readAsync("passwords", function(contents) {
writeAsync("world.txt", contents + "OWNED", callback);
});
}
This isn't so bad with just two callback actions, but it can quickly get pretty messy when you need to perform a lot of asynchronous steps in sequence. Promises are one mechanism that were devised to help manage this problem, and I recommend checking them out.
I am writing a javascript library to abstract ajax requests to my HTTP API.
Each of my javascript functions is a wrapper for jquery's ajax call, which makes a callback to the user on completion.
Eg.
mylib.doThing( "foo", { success:function(){alert("done");});
In the case where I want to execute mylib.doFoo twice in series, I have something like:
mylib.doThing( "foo", { success:function(){ mylib.doThing( "bar", { success:function(){alert("done");}); });
For anything more that two steps, this gets messy very quickly.
Is it possible to provide a cleaner syntax, perhaps more like the following? And how would I need to implement mylib.doThing()?
mylib.doThing("foo").mylib.doThing("bar").alert("done");
function doThingFluent(a, b) {
return {
_name : a,
_chainedCall : b,
doMoreThingFluent : function(a1) {
return doThing(a1, this);
},
done : function(callback) {
var chained = this._chainedCall;
var name = this._name;
while (chained) {
callback = function(n, c) {
return function() {
mylib.doThing(n, { success : c });
};
} (name, callback);
name = chained._name;
chained = chained._chainedCall;
}
mylib.doThing(name, {success: callback});
}
};
doThingFluent("foo").doMoreThingFluent("bar").done(function(){alert("done");})
If you just want to chain unspecified number of successful requests with doing something in the end, listing all things to do as a natural list instead of chain of methods would be even cleaner:
mylib.doThing("foo", "bar", /* all done callback -> */ function() { alert("done") })
doThing would have inside a factory that would create either nested callbacks or - even better - iterative manager that'd run all requests in sequence and then call final callback.
Consider the following function using jQuery:
function getVal() {
jQuery.get('/relative/url/', function (data) {
return data.getElementById('myInput').value;
}
}
This is basically what I want to do, but I have no idea how it should be done.
The only methods I know would work involve frames or innerHTML which I can't use because I have to wait for the element to be ready. The only way to do that is to use a callback, and this function must return the value of the element rather than something else.
My logic is likely faulty here, so please feel free to correct me.
First of all, with your current structure you should use a callback to return the value. To parse the HTML string retrieved via AJAX, you can hand it to jQuery and then query it just as usual.
function getVal(callback) {
jQuery.get('/relative/url/', function (data) {
callback($(data).find('#myInput').val());
}, 'html');
}
Then, when you are calling the function getVal, you'll need to provide a callback:
getVal(function(input_val) {
/**
* This code will be run once the GET request finishes.
* It will be passed one parameter - the value of #myInput in the HTML
* response to the request (see getVal function).
*/
alert(input_val);
});
No, you could not do that.. since it is ansync call. What you need is to provide a callback to you code, to return the value
function getVal(callback) {
jQuery.get('/relative/url/', function (data) {
callback(data.getElementById('myInput').value);
}
}
getVal(function (value) {
alert(value);
});
if the it's valid html markup, you can use browse its xml with the selector:
*[id=myInput]
or you can just render the markup on some dummy element in your page and then do you lookup:
function getVal() {
jQuery.get('/relative/url/', function (data) {
dummyNode.innerHTML = data; //not sure data points to responseTxt
return getElementById('myInput').innerHTML;
}
}
You cannot do that unless the elements are added to dom tree.
function getVal() {
jQuery.get('/relative/url/', function (data) {
return $(document.body).append(data).find('#myInput').val();
}
}
Several problems there. First, you cannot return from a callback like that. You would just return to the anonymous function itself, not from the getVal() method.
To solve that, you can return the jXHR object and apply some magic:
function getVal() {
return jQuery.get('/relative/url/');
}
getVal().done(function (data) {
var val = $( data ).find('#myInput').val();
// do something with val
}
I don't know how the structure from data looks like, but it should work that way. If not, its probably because of myInput is on the top level. In that case, replace .find() with .filter().
Oh, alright. I've got it. I don't think I provided enough information. I assumed context was irrelevant. Alright, here's an example depicting my solution.
function getVal() {
$.get('/relative/url/', function (data) {
$.get('relative/url/2', function (data2) {
var data_obj = [];
data_obj.push({
"data_1":data[i].name
}, {
"data_2":$(data).find('#myInput').val()
});
});
}
}