Using jQuery.when with array of deferred objects causes weird happenings with local variables - jquery-deferred

Let's say I have a site which saves phone numbers via an HTTP call to a service and the service returns the new id of the telephone number entry for binding to the telephone number on the page.
The telephones, in this case, are stored in an array called 'telephones' and datacontext.telephones.updateData sends the telephone to the server inside a $.Deferred([service call logic]).promise();
uploadTelephones = function (deffered) {
for (var i = 0; i < telephones.length; i++){
deffered.push(datacontext.telephones.updateData(telephones[i], {
success: function (response) {
telephones[i].telephoneId = response;
},
error: function () {
logger.error('Stuff errored');
}
}));
}
}
Now if I call:
function(){
var deferreds = [];
uploadTelephones(deferreds);
$.when.apply($, deferreds)
.then(function () {
editing(false);
complete();
},
function () {
complete();
});
}
A weird thing happens. All the telephones are sent back to the service and are saved. When the 'success' callback in uploadTelephones method is called with the new id as 'response', no matter which telephone the query relates to, the value of i is always telephones.length+1 and the line
telephones[i].telephoneId = response;
throws an error because telephones[i] does not exist.
Can anyone tell me how to keep the individual values of i in the success callback?

All of your closures (your anonymous functions capturing a variable in the local scope) refer to the same index variable, which will have the value of telephones.length after loop execution. What you need is to create a different variable for every pass through the for loop saving the value of i at the instance of creation at for later use.
To create a new different variable, the easiest way is to create an anonymous function with the code that is to capture the value at that particular place in the loop and immediately execute it.
either this:
for (var i = 0; i < telephones.length; i++)
{
(function () {
var saved = i;
deffered.push(datacontext.telephones.updateData(telephones[saved],
{
success: function (response)
{
telephones[saved].telephoneId = response;
},
error: function ()
{
logger.error('Stuff errored ');
}
}));
})();
}
or this:
for (var i = 0; i < telephones.length; i++)
{
(function (saved) {
deffered.push(datacontext.telephones.updateData(telephones[saved],
{
success: function (response)
{
telephones[saved].telephoneId = response;
},
error: function ()
{
logger.error('Stuff errored ');
}
}));
})(i);
}
should work.
Now, that's a bit ugly, though. Since you are already going through the process of executing an anonymous function over and over, if you want your code to be a little bit cleaner, you might want to look at Array.forEach and just use whatever arguments are passed in, or just use jQuery.each as you are already using jQuery.

Related

How do I use promises in a Chrome extension?

What I am trying to do is create a chrome extension that creates new, nested, bookmark folders, using promises.
The function to do this is chrome.bookmarks.create(). However I cannot just
loop this function, because chrome.bookmarks.create is asynchronous. I need to wait until the folder is created, and get its new ID, before going on to its children.
Promises seem to be the way to go. Unfortunately I cannot find a minimal working example using an asynchronous call with its own callback like chrome.bookmarks.create.
I have read some tutorials 1, 2, 3, 4. I have searched stackOverflow but all the questions do not seem to be about plain vanilla promises with the chrome extension library.
I do not want to use a plugin or library: no node.js or jquery or Q or whatever.
I have tried following the examples in the tutorials but many things do not make sense. For example, the tutorial states:
The promise constructor takes one argument—a callback with two
parameters: resolve and reject.
But then I see examples like this:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
How this works is a mystery to me.
Also, how can you call resolve() when its never been defined? No example in the tutorials seem to match real life code. Another example is:
function isUserTooYoung(id) {
return openDatabase() // returns a promise
.then(function(col) {return find(col, {'id': id});})
How do I pass in col, or get any results!
So if anyone can give me a minimal working example of promises with an asynchronous function with its own callback, it would be greatly appreciated.
SO wants code, so here is my non-working attempt:
//loop through all
function createBookmarks(nodes, parentid){
var jlen = nodes.length;
var i;
var node;
for(var i = 0; i < nodes.length; i++){
var node = nodes[i];
createBookmark(node, parentid);
}
}
//singular create
function createBookmark(node, parentid){
var bookmark = {
parentId : parentid,
index : node['index'],
title : node['title'],
url : node['url']
}
var callback = function(result){
console.log("creation callback happened.");
return result.id; //pass ID to the callback, too
}
var promise = new Promise(function(resolve, reject) {
var newid = chrome.bookmarks.create(bookmark, callback)
if (newid){
console.log("Creating children with new id: " + newid);
resolve( createBookmarks(bookmark.children, newid));
}
});
}
//allnodes already exists
createBookmarks(allnodes[0],"0");
Just doesn't work. The result from the callback is always undefined, which it should be, and I do not see how a promise object changes anything. I am equally mystified when I try to use promise.then().
var newid = promise.then( //wait for a response?
function(result){
return chrome.bookmarks.create(bookmark, callback);
}
).catch(function(error){
console.log("error " + error);
});
if (node.children) createBookmarks(node.children, newid);
Again, newid is always undefined, because of course bookmarks.create() is asynchronous.
Thank you for any help you can offer.
Honestly, you should just use the web extension polyfill. Manually promisifying the chrome APIs is a waste of time and error prone.
If you're absolutely insistent, this is an example of how you'd promisify chrome.bookmarks.create. For other chrome.* APIs, you also have to reject the callback's error argument.
function createBookmark(bookmark) {
return new Promise(function(resolve, reject) {
try {
chrome.bookmarks.create(bookmark, function (result) {
if (chrome.runtime.lastError) reject(chrome.runtime.lastError)
else resolve(result)
})
} catch (error) {
reject(error)
}
})
}
createBookmark({})
.then(function (result) {
console.log(result)
}).catch(function (error) {
console.log(error)
})
To create multiple bookmarks, you could then:
function createBookmarks(bookmarks) {
return Promise.all(
bookmarks.map(function (bookmark) {
return createBookmark(bookmark)
})
)
}
createBookmarks([{}, {}, {}, {}])
.catch(function (error) {
console.log(error)
})
Take the advantage of the convention that the callback function always be the last argument, I use a simple helper function to promisify the chrome API:
function toPromise(api) {
return (...args) => {
return new Promise((resolve) => {
api(...args, resolve);
});
};
}
and use it like:
toPromise(chrome.bookmarks.create)(bookmark).then(...);
In my use case, it just works most of the time.

Why is async.map passing only the value of my JSON?

I have a function in node.js that looks like this:
exports.getAllFlights = function(getRequest) {
// this is the package from npm called "async"
async.map(clients, getFlight, function(err, results) {
getRequest(results);
});
}
The variable clients should be a JSON that looks like this:
{'"A4Q"': 'JZA8187', "'B7P"': 'DAL2098' }.
I expect that the map function will pass the individual indices of the array of the variable clients to getFlight. However, instead it passed the values of that each(ex: 'DAL2098', 'JZA8187' and so on).
Is this the expected functionality? Is there a function in async that will do what I want?
The signature of getFlight is getFlight(identifier, callback). Identifier is what is currently messed up. It returns callback(null, rtn). Null reprsents the nonexistence of an error, rtn represents the JSON that my function produces.
Yes, that's the expected result. The documentation is not very clear but all iterating functions of async.js pass the values of the iterable, not the keys. There is the eachOf series of functions that pass both key and value. For example:
async.eachOf(clients, function (value, key, callback) {
// process each client here
});
Unfortunately there is no mapOf.
If you don't mind not doing things in parallel you can use eachOfSeries:
var results = [];
async.eachOfSeries(clients, function (value, key, callback) {
// do what getFlight needs to do and append to results array
}, function(err) {
getRequest(results);
});
Another (IMHO better) workaround is to use proper arrays:
var clients = [{'A4Q': 'JZA8187'},{'B7P': 'DAL2098'}];
Then use your original logic. However, I'd prefer to use a structure like the following:
var clients = [
{key: 'A4Q', val: 'JZA8187'},
{key: 'B7P', val: 'DAL2098'}
];
First create a custom event. Attach a listener for return data. then process it.
var EventEmitter = require('events');
var myEmitter = new EventEmitter();
myEmitter.emit('clients_data',{'"A4Q"': 'JZA8187'}); //emit your event where ever
myEmitter.on('clients_data', (obj) => {
if (typeof obj !=='undefined') {
if (obj.contructor === Object && Object.keys(obj).lenth == 0) {
console.log('empty');
} else {
for(var key in obj) {
var value = obj[key];
//do what you want here
}
}
}
});
Well, you need to format your clients object properly before you can use it with async.map(). Lodash _.map() can help you:
var client_list = _.map(clients, function(value, key) {
var item = {};
item[key] = value;
return item;
});
After that, you will have an array like:
[ { A4Q: 'JZA8187' }, { B7P: 'DAL2098' } ]
Then, you can use async.map():
exports.getAllFlights = function(getRequest) {
async.map(client_list, getFlight, function(err, results) {
getRequest(results);
});
};

Comparing user input to some fields in an array of JSON objects

I have a webserver with JSON data in it. This is what my data looks like
[
{
iduser: 1,
username: "joe",
password: "****"
},
{
iduser: 2,
username: "gina",
password: "****"
}
]
In my app I take some user input and wish to compare it to the username and password field. Here is where I check the data
.service('LoginService', function ($q, $http) {
return {
loginUser: function (name, pw) {
var deferred = $q.defer();
var promise = deferred.promise;
var user_data = $http.get("http://<my ip address>:<port>/login");
user_data.then(function ($scope, result) {
$scope.user = result.data;
})
for (var x in $scope.user) {
if (name == x.username && pw == x.password) {
deferred.resolve('Welcome ' + name + '!');
} else {
deferred.reject('Wrong credentials.');
}
}
promise.success = function (fn) {
promise.then(fn);
return promise;
}
promise.error = function (fn) {
promise.then(null, fn);
return promise;
}
return promise;
}
}
})
I am still learning angularJS and I know this is not a secure way to check the data I just want this loop to work.
My understanding of what I have here is that $scope.user holds my JSON data. Then the data is cycled through with the for loop and the user input name is compared to the field username of each iteration. But this is not the case as I am getting a fail every time.
I'm almost certain its a syntax error, but I don't know JavaScript or AngularJS well enough to find the problem. Any help is really appreciated, Thanks.
Edit 1
After what Nujabes said I made some changes since I don't need $scope.
//previous code the same
user_data.then(function (result) {
var user = result.data;
})
for (var x in user) {
if (name == x.username && pw == x.password) {
//prior code the same
I don't think var can hold the data and thats why I'm still getting errors. I think it should be in an array.
I think your syntax error is that you omit $scope.
You should inject $scope service to this line:
.service('LoginService',function($q,$http,$scope){ ...
});
And this code :
user_data.then(function ($scope, result) {
$scope.user = result.data;
});
Omit the $scope.
->
user_data.then(function (result) {
$scope.user = result.data;
});
like this.
Give it a try.
I hope it work.
(However, why do you want to use $scope service in your 'service'?
I think, defining local value and returning some method is a better choice.
and you use the $scope service in your 'controller'.)
$scope.user you are trying to loop through is array right ?
using (for/in) will store the key in the variable x which is in your case the index of each element (0,1,2,..) , to loop through arrays use (for/of) like this :
for (var value of array)
this will give you the values ...

AngularJS and Dreamfactory: $resource.save() not saving all the records in loop or in queue

I'm trying to save the data using $resource and for REST API using Dreamfactory. I do have seperate mysql table for multiple addresses as user might have multiple addresses.
I'm looping through multiple addresses and querying to the API but some how it doesn't store in sequence or split some of the data to store
Here is my code:
Members.save($scope.member,
function () {
//console.log('POST',arguments );
console.log('POST', arguments[0].id);
$scope.mid = arguments[0].id;
if($scope.addresses)
{
$.each($scope.addresses,function(k,v){
//alert(k+"=>"+v);
Members_address.save({"member_id":$scope.mid,"address":v},
function () {
alert(k+"=>"+v);
console.log('POST',arguments );
}, function () {
console.error('POST', arguments);
}
);
});
window.location = "#/members";
}else
{
window.location = "#/members";
}
}, function () {
console.error('POST', arguments);
}
);
"Members" is factory to store data into members table and "Members_address" is a factory to store data in separate members_address table with member id.
It stores addresses but not in sequence and sometime it missed one of the address.
Here are the factories:
App.factory('Members',['$resource',function ($resource) {
return $resource('API_URL', null, {'update': { method:'PUT' }
});
}])
App.factory('Members_address',['$resource',function ($resource) {
return $resource('API_URL', null, {'update': { method:'PUT' }
});
}])
Check this example at JSFiddle using recursion, I think this is what you want.
Ok. So, one fundamental thing is the thought process when you working with data like this (especially with DreamFactory). You want to create a 'payload' of data to send to the server rather that trying to iteratively save records. Make an array of the records of a specific type that you want to save and then push them up to the server. In this case...you want to save a member(single object) and then addresses associated with that member(array of objects). The process is 'send member record to server then on save success, if I have addresses, create address objects with member id and store them in an array and send to the server. Then on address save success redirect.'
So here's the code I came up with. Hope it helps you out. Also, checkout $location for redirecting as opposed to window.location. And if your in need of the window object checkout $window. And...try only to use jQuery inside of directives to do DOM manipulation. AngularJS provides a lot of functionality for manipulating data.
// I'm assuming that your 'API_URL' contains the following information shown
// in this factory definition.
App.factory('Members', ['$resource', function ($resource) {
return $resource('YOUR_DSP_URL/TABLE_NAME', null, {'update': { method:'PUT' } });
}]);
App.factory('Members_address', ['$resource', function ($resource) {
return $resource('YOUR_DSP_URL/TABLE_NAME', null, {'update': { method:'PUT' } });
}]);
App.controller('SomeCtrl', ['Members', 'Members_address', '$scope', '$location', function(Members, Members_address, $scope, $location) {
$scope.member = {}; // Member data object
$scope.addresses = []; // Array of address strings
// attach to UI button to trigger the save
$scope.saveMember = function() {
// Save the member
Member.save($scope.member).then(
// Handle Member save success
function(result) {
// Check for addresses
if ($scope.addresses) {
// Create a temporary var to hold our payload
// to the server
var payload = [];
// Assemble address objects for insertion in db
angular.forEach($scope.addresses, function (_string) {
// create a temporary var to hold our address
var tempAddressObject = {};
// add member id
tempAddressObject['member_id'] = $scope.member.id;
// add address
tempAddressObject['address'] = _string;
// store on temporary payload array;
payload.push(tempAddressObject);
});
// Check that we have some records in our payload
if (payload.length > 0) {
// Send to the server
Members.address.save(payload).then(
// Handle Success
function(result) {
console.log(result);
// redirect
$location.url('/members');
},
// handle Error
function (reject) {
console.log(error);
// redirect
$location.url('/members');
}
)
}
}
},
// Handle Member save error
function(reject) {
console.log(reject);
}
);
}
}]);

Get multi parameter values one-time from chrome.storage.local

I wanna inject a content script to pages with a method with two parameters. Since the Chrome.storage.local.get() is asynchronous, I have to make sure that all parameters are initialized from the chrome.local.storage and then begin to invoke my method.
Now I can get only one parameter every time with code like :
var parameter1;
storage.get('parameter1', function(items) {
parameter1 = items.parameter1;
if (parameter1) {
//do sothing
}
});
var parameter2;
storage.get('parameter2', function(items) {
parameter2 = items.parameter2;
if (parameter2) {
//do sothing
}
});
But this is not enough to call my method which contains 2 parameters:
function myMethod(parameter1, parameter2);
So how to get and retrieve them ?
If anyone comes back here in 2021 :)
chrome.storage.sync.get(["CONST", "user", "devMode"], (res) => {
reallyAwesomeFunction(res.CONST.PI, res.user.id, res.devMode);
});
You can try something like this -
function myMethod(parameter1, parameter2) {
var details = [parameter1, parameter2];
storage.get(details, function(items) {
alert(items.parameter1 + ' '+items.parameter2);
});
}