So, I've built an api object that can be included in any JavaScript file via require.js. In the api object, I have calls to create Backbone models/collections like the one shown below:
getDatapoints: function(attributes, callback) {
require(['models/datapoint'], function(Datapoint){
var datapoint = new Datapoint.DatapointCollection(attributes);
datapoint.fetch({success: function(data){
return callback(data.toJSON());
}});
});
}
I want to have a way of being able to start multiple calls and run a callback function once ALL calls have completed. It looks like jQuery's $.when function does what I want, but I'm not sure how to get it to work with anything besides $.ajax calls.
Am I looking in the right place? Should I be looking at something like q.js?
Expanding on #mattacular's answer:
API = {
getDatapoints: function (attributes){
var dfd = $.Deferred();
require(['models/datapoint'], function(Datapoint){
var dataPoints = new Datapoint.DatapointCollection(attributes);
dataPoints.fetch().then(function (points){
dfd.resolve(points.toJSON());
}, function (error){
dfd.reject(error);
});
});
return dfd.promise();
},
getAllDatapoints: function (arrayOfAttributes){
var arrayOfPromises = arrayOfAttributes.map(this.getDatapoints);
return $.when.apply($, arrayOfPromises);
}
}
And where you're actually calling the getAllDatapoints method:
var allDatapointAttributes = [{...}, {...}, {...}];
API.getAllDatapoints(allDatapointAttributes).done(function(){
console.log.apply(console, arguments);
// should output an array of arrays containing dataPoint
// objects when all the requests have completed successfully.
});
You can do this using jQuery's Deferred object. Here is a quick example:
getDatapoints: function (attributes, callback() {
var dataPoints = $.Deferred();
// perform async calls here
// when "done," call dataPoints.resolve() or dataPoints.reject() accordingly
return dataPoints.promise();
}
edit: removed outdated tutorial
Related
I am trying to use jQuerys $.when() to load a bunch of localization resources before initializing the control on the client side:
var fooControl = (function($, kendo, _) {
var
initResources = function() {
return $.when(
window.clientResources.getAll("Messages").done(function(d) {
resources["Messages"] = d;
}),
window.clientResources.getAll("Cost").done(function(d) {
resources["Cost"] = d;
})
);
},
init = function(options) {
/*... */
}
}
//calling:
$(function() {
fooControl.initResources().then(fooControl.init(options));
});
What I want to achieve is, that initResources waits until the resources are loaded up and assigned to their variables. They are either loaded up from an API endpoint or localStorage, if the data is cached.
What actually happens is, that I am receiving an error Cannot read property 'CostType' of undefined, which indicates, that the Cost resources haven't been fully loaded yet.
So I suspect, that the calls to window.clientResources.getAll() are being resolved properly, but not the following .done method and this then results in a race condition, the resources are losing.
How can I make sure, that the whole call stack, including the assignment of the resources variable has been resolved and only then the following init function is called?
You are invoking the init immediately and passing its return value as success callback handler, A simple solution would be to use a anonymous method
fooControl.initResources().then(function(){
fooControl.init(options);
});
You could use $.Deferred() for this, then resolve that only when the resources have been loaded.
Also as Satpal noted, then needs to be passed either an anonymous function or a function reference.
var fooControl = (function($, kendo, _) {
var initResources = function() {
var deferred = $.Deferred();
$.when(
window.clientResources.getAll("Messages"),
window.clientResources.getAll("Cost")
).done(function(msgData, costData) {
resources["Messages"] = msgData;
resources["Cost"] = costData;
deferred.resolve();
});
return deferred.promise();
},
init = function(options) {
/*... */
}
}
//calling:
$(function() {
fooControl.initResources().then(fooControl.init.bind(this, options));
});
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 have the following function which queries a SQLite database and pushed results into an Array that is used later. I need to wait for the each statement to process the tables in the (self.projectSetEditList) However it seems that the master deferred is not waiting for all promises... Am I just going about this all wrong? I need to know when all the sql results are ready before proceeding to the next function.
/// <summary>Validates user INSERTS to project_set entities</summary>
this.initProjectSetAdds = function ()
{
var promises = [];
var masterDeferred = new $.Deferred();
///var count = (self.projectSetEditList.length - 1);
$.each(self.projectSetEditList, function (index, syncEntity)
{
var def = new $.Deferred();
//get the config entity definition object
var entityDefinition = self.getEntityDefinition(syncEntity.entity_name);
self.db.executeSql(self.getAddsSql(entityDefinition)).done(function (tx, insertResults)
{
self.projectSetAdds.push({ definition: entityDefinition, addedObjects: dataUtils.convertToObjectArray(insertResults) });
def.resolve(true);
});
promises.push(def);
});
//resolve all deferred and return to caller
$.when.apply($, promises).then(function ()
{
masterDeferred.resolve(arguments);
},
function ()
{
masterDeferred.reject(arguments);
});
return (masterDeferred.promise());
}
The only async function inside is executeSql... Any suggestions are greatly appreciated
Your code appears unnecessarily complicated to me.
Since $.when also creates a promise, don't bother creating the masterDeferred yourself, just do:
return $.when.apply($, promises);
The only functional difference is that this version will pass the true results as individual parameters to the eventual callback, whereas your code will pass a single array of [true, true, ...] values.
I need to run a series of calls over websockets via Socket.IO (client-side). Since I'm not using $.ajax, jQuery's deferred functions won't integrate as well and I'll have to manually handle promises. With every websocket call, I pass a callback and I'm quickly seeing how this project could spiral out of control. Here's a simplified example of how my websocket calls work (excluding all connection handling code):
function js2node(nodeFunction, data, callback){
socket.emit('incoming', nodeFunction, data, callback);
}
function sampleServerCall(){
js2node('sampleCall', 'something', 'sampleCallback');
}
function sampleCallback(json){
// Handle data
}
sampleServerCall();
I will be talking to the server quite a bit, all calls will be asynchronous, but some will need to come back in a specific order. Enter jQuery deferred. Here is some working code:
var deferredArray = [];
$(function(){
$.when( // Any order
getData1(),
getData2()
).then(function(){ // Must have responses from dataCallback1 and dataCallback2 before doing this...
$.when( // Any order
getData3(),
getData4()
).then(function(){ // Must have responses from dataCallback3 and dataCallback4 before doing this...
getData5();
});
});
});
function getData1(){
js2node('data1', 'something', 'dataCallback1');
deferredArray[0] = new $.Deferred();
return deferredArray[0].promise();
}
function getData2(){
js2node('data2', 'something', 'dataCallback2');
deferredArray[1] = new $.Deferred();
return deferredArray[1].promise();
}
function getData3(){
js2node('data3', 'something', 'dataCallback3');
deferredArray[2] = new $.Deferred();
return deferredArray[2].promise();
}
function getData4(){
js2node('data4', 'something', 'dataCallback4');
deferredArray[3] = new $.Deferred();
return deferredArray[3].promise();
}
function getData5(){
js2node('data5', 'something', 'dataCallback5');
deferredArray[4] = new $.Deferred();
return deferredArray[4].promise();
}
function dataCallback1(json){
// Handle data
deferredArray[0].resolve();
}
function dataCallback2(json){
// Handle data
deferredArray[1].resolve();
}
function dataCallback3(json){
// Handle data
deferredArray[2].resolve();
}
function dataCallback4(json){
// Handle data
deferredArray[3].resolve();
}
function dataCallback5(json){
// Handle data
deferredArray[4].resolve();
}
As you can see, I'm still stuck with nested callbacks from the way I'm using when/then and nesting could potentially go deeper as I add functionality. Deferred is a new concept to me but I've read it's supposed to help in situations such as this. I feel like there has to be a better way than what I'm currently doing. Can anyone help me set this up more efficiently?
You can do more with .then:
$(function(){
$.when(
doSock('data1', 'something'),
doSock('data2', 'something')
).then(function(data1, data2){
return $.when(
doSock('data3', 'something'),
doSock('data4', 'something')
);
}).then(function(data3, data4){
return doSock('data5', 'something');
});
});
That way your nesting never goes deeper than that.
(i used adeneo's helper method)
Using a better helper function sure would help, but you'd still have to structure the calls with $.when and $.then to execute them in the proper order
function doSock(nodeFunction, data) {
var def = new $.Deferred();
socket.emit('incoming', nodeFunction, data, function(received) {
def.resolve(received)
});
return def.promise();
}
$(function(){
$.when(
doSock('data1', 'something'),
doSock('data2', 'something')
).then(function(data1, data2){
$.when(
doSock('data3', 'something'),
doSock('data4', 'something')
).then(function(data3, data4){
doSock('data5', 'something');
});
});
});
var ajaxStuff = (function () {
var doAjaxStuff = function() {
//an ajax call
}
return {
doAjaxStuff : doAjaxStuff
}
})();
Is there any way to make use of this pattern, and fetch the response from a successful ajaxcall when calling my method? Something like this:
ajaxStuff.doAjaxStuff(successHandler(data){
//data should contain the object fetched by ajax
});
Hope you get the idea, otherwise I'll elaborate.
Two things:
1. Add a parameter to the doAjaxStuff function.
2. When invoking doAjaxStuff, pass in an anonymous function (or the name of a function)
var ajaxSuff = (function () {
var doAjaxStuff = function(callback) {
// do ajax call, then:
callback(dataFromAjaxCall);
}
return {
doAjaxStuff : doAjaxStuff
}
})();
// calling it:
ajaxStuff.doAjaxStuff(function(data){
//data should contain the object fetched by ajax
});
Just let doAjaxStuff accept a callback:
var doAjaxStuff = function(callback) {
// an ajax call
// Inside the Ajax success handler, call
callback(response); // or whatever the variable name is
}
Depending on your overall goals, you could also use deferred objects instead (or in addition). This makes your code highly modular. For example:
var doAjaxStuff = function() {
// $.ajax is just an example, any Ajax related function returns a promise
// object. You can also create your own deferred object.
return $.ajax({...});
}
// calling:
ajaxStuff.doAjaxStuff().done(function(data) {
// ...
});
I believe you need to read the jQuery docs for jQuery.ajax. You could make a call as simple as:
$.ajax('/path/to/file').success(function (data) {
doStuff();
})