pass loop parameters to closure inside closure - javascript

I'm trying to give the for loop parameter i to the inner closure because I want to identify my decoded audio (that's put inside buffer).
This code gives an error: e is undefined. It works however when removing the )(test) by which I mean that test is equal to list.length for all the results however I want them to have the value of the current parameter i when called.
for (var i = 0; i < list.length; i++) { //load in every url
requestArray.push(new XMLHttpRequest());
requestArray[i].open('GET', list[i].url, true);
requestArray[i].responseType = 'arraybuffer';
test = i;
requestArray[i].onload = (function (e) {
//Async method: ASK J
context.decodeAudioData(e.target.response, (function (buffer) { //Async method
console.log(test);
if (!buffer) {
alert('error decoding file data: ');
return;
}
})(test),
function (e) {
console.log('Error decoding audio file', e)
});
})(test);
requestArray[i].onerror = function () {
alert('BufferLoader: XHR error');
}
requestArray[i].send();
}

for (var i=0; i<list.length; i++){
requestArray.push(new XMLHttpRequest());
requestArray[i].open('GET', list[i].url, true);
requestArray[i].responseType = 'arraybuffer';
requestArray[i].onload = (function (i) {
return function (resp) {
// i: index in requestArray
// resp: the response object passed when the onload event occurs
context.decodeAudioData(
resp.target.response,
(function(test) {
return function (buffer) {
console.log(test);
if (!buffer) {
alert('error decoding file data: ');
return;
}
}
}(i)),
function(e) { console.log('Error decoding audio file', e)}
);
}
}(i));
requestArray[i].onerror = function() {
alert('BufferLoader: XHR error');
}
requestArray[i].send();
}
Please note that for a closure to be created a function must return a function.
This is a closure:
(function(){
var a = "b";
return function(){ alert(b); }
}());
This is evaluated AS SOON as it is seen:
(function(){
var a = "b";
}());

Your code does not do what you want it to. Instead of binding test as an argument, you immediately call your anonymous functions with the argument test and pass their returned results as arguments to decodeAudioData. I'd suggest fixing this first. E.g. by using Function.prototype.bind.

I guess I would try a syntax like this (I would also take a more modular approach, with several declared function)
for (var i=0; i<list.length; i++){ //load in every url
requestArray.push(new XMLHttpRequest());
requestArray[i].open('GET', list[i].url, true);
requestArray[i].responseType = 'arraybuffer';
test = i;
requestArray[i].onload = (function(outerIndex){
return function (e) { //Async method: ASK J
context.decodeAudioData(e.target.response,
(function(index){
return function(buffer) { //Async method
console.log(index);
if (!buffer) {
alert('error decoding file data: ');
return;
}
};
})(outerIndex), function(e) { console.log('Error decoding audio file', e)});
};})(test);
requestArray[i].onerror = function() {
alert('BufferLoader: XHR error');
}
requestArray[i].send();
}

When you put
})(test);
You run the function at that time (not when the event occurs) passed test like e.
I don't understand "e is undefined" e is i.
Could you try to put "var" where you define the variable test and remove ")(test)"? I think the issue is in scope of test.

Related

chrome.webRequest.onBeforeRequest.addListener not blocking an array of urls

I am trying to block an array of urls based on user input. I have the url array in JSON format, but the sites are not actually being blocked when I navigate to them. If I use only one site, instead of an array, it does get blocked successfully. Here is the function.
function addListener(){
chrome.webRequest.onBeforeRequest.addListener(
function(){ return {cancel: true}; },
{
urls: blockedUrls()
},
["blocking"]
);
}
And here is my function blockedUrls.
var blockedUrls = function () {
chrome.storage.sync.get(['block'], function(result) {
if (typeof result.block === 'undefined') {
//blocks is not yet set
var jobj = ["*://www.whatever.com/*"];
return [jobj[0]];
console.log("not set");
}
else{
var xt = JSON.parse(result.block);
console.log(JSON.stringify(xt.urls));
return JSON.stringify(xt.urls);
}
});
return ["*://www.whatever.com/*"];
}
The console.log does print out what I want, which is this (some were just used for testing obviously)
["doesntexist.com","*://www.yahoo.com/*","*://www.xbox.com/*","*://www.hello.com/*","*://www.es/*"]
And, if it helps, here is where the sites get initially set into chrome storage, from the variable request.newSites.
var jsonStr = '{"urls":["doesntexist.com"]}';
var obj = JSON.parse(jsonStr);
//add url matching patterns to the urls from user input
for (var i = 0; i < request.newSite.length; i++){
obj['urls'].push( '*://www.' + request.newSite[i] + '/*');
}
jsonStr = JSON.stringify(obj);
chrome.storage.sync.set({'block': jsonStr}, function(){
addListener();
});
Thanks in advance.
There are a couple of problems with your code:
1) chrome.storage.sync.get's callback function is asynchronous. Therefore, in your function blockedUrls the return value will always be ["*://www.whatever.com/*"], because the line return ["*://www.whatever.com/*"]; will run before chrome.storage.sync.get's callback function.
2) The second argument of chrome.webRequest.onBeforeRequest listener should be an object in the following form:
{urls: theUrls} where theUrls is an Array of strings, not a string.
Apart from that, you can take advantage of the fact that chrome.storage can store objects and arrays directly, so there is no need to stringify them.
Try with:
var obj = {urls: ['*://doesntexist.com/*']};
for (var i = 0, j = request.newSite.length; i < j; i++){
obj.urls.push( '*://www.' + request.newSite[i] + '/*');
}
chrome.storage.sync.set({block: obj}, function(){
addListener();
});
function addListener() {
chrome.storage.sync.get('block', function (result) {
var myUrls = result.block || ["*://www.whatever.com/*"];
chrome.webRequest.onBeforeRequest.addListener(function(){
return {cancel: true}
},
{urls: myUrls},
["blocking"] );
});
}
chrome.storage.sync.get is an asyncronous function. Therefor it will not return your url list.
What you probably meant to do was the following:
function addListener(){
chrome.storage.sync.get(['block'], function(result) {
let urls;
if (typeof result.block === 'undefined') {
//blocks is not yet set
var jobj = ["*://www.whatever.com/*"];
urls = [jobj[0]];
console.log("not set");
}
else{
var xt = JSON.parse(result.block);
console.log(JSON.stringify(xt.urls));
urls = JSON.stringify(xt.urls);
}
chrome.webRequest.onBeforeRequest.addListener(
function(){ return {cancel: true}; },
{
urls: urls
},
["blocking"]
);
});
}

Can I stop an event handler while it's executing?

Is it possible to stop the execution of a previous event when the event is called again?
To clarify, I have a button <button onclick='load()'>load</button> that calls a load() function which gets an array, processes each element and displays it in a list <ul id='main'></ul>
function load(event) {
$("#main").empty(); //empty old elements
$.get("load.php", '', function (data) {
var arr = JSON.parse(data);
for (i = 0; i < arr.length; ++i) {
process(arr[I]); //process and append to #main
}
});
}
Problem is, that if I click the button again while its still putting the elements into the array, I get the new list plus the rest of the old list.
Is there a way to stop the first event while its still executing but still execute the second event?
You should try this:
var xhr;
function load(ev){
if(ev.eventPhase === 2){
if(xhr)xhr.abort();
$('#main').empty();
xhr = $.get('load.php', function(data){
var a = JSON.parse(data);
for(var i=0,l=a.length; i<l; i++){
process(a[i]);
}
});
}
}
I can be wrong, but...
var req = $.ajax({
$("#main").addEventListener("click",()=>{req.abort()})
...
...
$("#main").removeEventListener("click",()=>{req.abort()})
});
As noted, you can stop the event by setting a flag and checking it, but a better approach would simply be to assign the new value directly. If your code works it means JSON.parse is returning an array already.
That means
"use strict";
(function () {
function load(event) {
$("#main").empty();
$.get("load.php", '', function (data) {
process = JSON.parse(data);
$("#main").whateverMethodFillsTheElement(process);
});
}());
Also, when writing asynchronous JavaScript code that makes HTTP requests, promises are preferred to callbacks. Since $.get returns a Promise you can write
"use strict";
(function () {
function load(event) {
$("#main").empty();
$.get("load.php")
.then(function (data) {
var items = JSON.parse(data);
$("#main").whateverMethodFillsTheElement(items);
});
}
}());
As discussed in comments, the aim is to use each item in another request which provides the actual value to add to 'main'. So loading data triggers an asynchronous call for each loaded item.
To accommodate this, we need to determine a key field that we can use to track each item so we do not append existing items to the list. We will call this field id for the sake of exposition.
"use strict";
(function () {
var allItems = [];
function load(event) {
$("#main").empty();
$.get("load.php")
.then(function (data) {
return JSON.parse(data);
})
.then(function (items) {
items.forEach(item => {
processItem(item)
.then(function (processed) {
var existingItem = allItems.filter(i => i.id === item.id)[0];
if(existingItem) {
var existingIndex = allItems.indexOf(existingItem);
allItems[existingIndex] = processed;
}
else {
allItems.push(processed);
}
});
});
});
}
}());
Ok, seems like it's not possible to stop an Ajax success function after it began executing or to stop a past event without aborting the current one.
But the following solution worked for me so I figured I'll post it here:
var num = 0;
function load() {
var curNum = ++num;
$("#main").empty();
$.get("load.php", '', function (data) {
var arr = JSON.parse(data);
for (i = 0; i < arr.length; ++i) {
process(arr[i], curNum);
}
});
}
function process(item, curNum) {
if(curNum === num) { //don't process if a new request has been made
//get 'data' based on 'item'...
if(curNum === num) { //check again in case a new request was made in the meantime
$("#main").append(data);
}
}
}
I appreciate everyone's help.

Javascript object property not accessible in callback function

I have a html page with this javascript in the body section of the page
JSFiddle link (I'm new to jsfiddle, couldn't get it to work, any help on this is appreciated to...)
The Firebug console shows me 0 in request, undefined in onError and 0 in callback. I have two questions, why can't i access this.loadErrors in onError, and how would I implement this error counter?
EDIT: the source code
var loader = new Loader();
loader.request(callback);
function callback(){
console.log("loader.loadErrors in callback: " + loader.loadErrors);
}
function Loader(){
this.loadErrors = 0;
this.request=function(callback){
for (var i = 0; i < 3; i++){
console.log("this.loadErrors in request: " + this.loadErrors);
$.ajax("example", {error: onError}).done(callback);
}
}
function onError(){
console.log("this.loadErrors in onError: " + this.loadErrors);
// this.loadErrors++;
callback();
}
}
The code messed up this keyword.
onError is called from outside of your code and it's this points to an unknown object (probably null).
To prevent this from happening, you have one of the two options:
bind() the onError() function to your Loader before calling it:
this.request=function(callback){
for (var i = 0; i < 3; i++){
console.log("this.loadErrors in request: " + this.loadErrors);
$.ajax("example", {error: onError.bind(this)}).done(callback);
}
}
Bind it to the Loader during creation:
var _this = this;
function onError(){
console.log("this.loadErrors in onError: " + _this.loadErrors);
// _this.loadErrors++;
callback();
}

Parse.Cloud.job promise not working

What I am trying to do here are:
Remove all contents in a class first, because every day the events.json file will be updated. I have my first question here: is there a better way to remove all contents from a database class on Parse?
Then I will send a request to get the events.json and store "name" and "id" of the result into a 2D array.
Then I will send multiple requests to get json files of each "name" and "id" pairs.
Finally, I will store the event detail into database. (one event per row) But now my code will terminate before it downloaded the json files.
Code:
function newLst(results) {
var event = Parse.Object.extend("event");
for (var i = 0; i < results.length; i++){
Parse.Cloud.httpRequest({
url: 'https://api.example.com/events/'+ results[i].name +'/'+ results[i].id +'.json',
success: function(newLst) {
var newJson = JSON.parse(newLst.text);
var newEvent = new event();
newEvent.set("eventId",newJson.data.id);
newEvent.set("eventName",newJson.data.title);
newEvent.save(null, {
success: function(newEvent) {
alert('New object created with objectId: ' + newEvent.id);
},
error: function(newEvent, error) {
alert('Failed to create new object, with error code: ' + error.message);
}
});
},
error: function(newLst) {
}
});
}
};
Parse.Cloud.job("getevent", function(request, status) {
var event = Parse.Object.extend("event");
var query = new Parse.Query(event);
query.notEqualTo("objectId", "lol");
query.limit(1000);
query.find({
success: function(results) {
for (var i = 0; i < results.length; i++) {
var myObject = results[i];
myObject.destroy({
success: function(myObject) {
},
error: function(myObject, error) {
}
});
}
},
error: function(error) {
alert("Error: " + error.code + " " + error.message);
}
});
var params = { url: 'https://api.example.com/events.json'};
Parse.Cloud.httpRequest(params).then(function(httpResponse) {
var results = [];
var jsonobj = JSON.parse(httpResponse.text);
for (var i = 0; i < jsonobj.data.length; i++) {
var tmp2D = {"name":"id"}
tmp2D.name = [jsonobj.data[i].name];
tmp2D.id = [jsonobj.data[i].id];
results.push(tmp2D);
}
newLst(results);
}).then(function() {
status.success("run job");
}, function(error) {
status.error(error);
});
});
I think my original answer is correct as a standalone. Rather than make it unreadable with the additional code, here it is made very specific to your edit.
The key is to eliminate passed callback functions. Everything below uses promises. Another key idea is decompose the activities into logical chunks.
A couple of caveats: (1) There's a lot of code there, and the chances that either your code is mistaken or mine is are still high, but this should communicate the gist of a better design. (2) We're doing enough work in these functions that we might bump into a parse-imposed timeout. Start out by testing all this with small counts.
Start with your question about destroying all instances of class...
// return a promise to destroy all instances of the "event" class
function destroyEvents() {
// is your event class really named with lowercase? uppercase is conventional
var query = new Parse.Query("event");
query.notEqualTo("objectId", "lol"); // doing this because the OP code did it. not sure why
query.limit(1000);
return query.find().then(function(results) {
return Parse.Object.destroyAll(results);
});
}
Next, get remote events and format them as simple JSON. See the comment. I'm pretty sure your idea of a "2D array" was ill-advised, but I may be misunderstanding your data...
// return a promise to fetch remote events and format them as an array of objects
//
// note - this differs from the OP data. this will evaluate to:
// [ { "name":"someName0", id:"someId0" }, { "name":"someName1", id:"someId1" }, ...]
//
// original code was producing:
// [ { "name":["someName0"], id:["someId0"] }, { "name":["someName1"], id:["someId1"] }, ...]
//
function fetchRemoteEvents() {
var params = { url: 'https://api.example.com/events.json'};
return Parse.Cloud.httpRequest(params).then(function(httpResponse) {
var results = [];
var remoteEvents = JSON.parse(httpResponse.text).data;
for (var i = 0; i < remoteEvents.length; i++) {
var remoteEvent = { "name": remoteEvents[i].name, "id": remoteEvents[i].id };
results.push(remoteEvent);
}
return results;
});
}
Please double check all of my work above regarding the format (e.g. response.text, JSON.parse().data, etc).
Its too easy to get confused when you mix callbacks and promises, and even worse when you're generating promises in a loop. Here again, we break out a simple operation, to create a single parse.com object based on one of the single remote events we got in the function above...
// return a promise to create a new native event based on a remoteEvent
function nativeEventFromRemoteEvent(remoteEvent) {
var url = 'https://api.example.com/events/'+ remoteEvent.name +'/'+ remoteEvent.id +'.json';
return Parse.Cloud.httpRequest({ url:url }).then(function(response) {
var eventDetail = JSON.parse(response.text).data;
var Event = Parse.Object.extend("event");
var event = new Event();
event.set("eventId", eventDetail.id);
event.set("eventName", eventDetail.title);
return event.save();
});
}
Finally, we can bring it together in a job that is simple to read, certain to do things in the desired order, and certain to call success() when (and only when) it finishes successfully...
// the parse job removes all events, fetches remote data that describe events
// then builds events from those descriptions
Parse.Cloud.job("getevent", function(request, status) {
destroyEvents().then(function() {
return fetchRemoteEvents();
}).then(function(remoteEvents) {
var newEventPromises = [];
for (var i = 0; i < remoteEvents.length; i++) {
var remoteEvent = remoteEvents[i];
newEventPromises.push(nativeEventFromRemoteEvent(remoteEvent));
}
return Parse.Promise.when(newEventPromises);
}).then(function() {
status.success("run job");
}, function(error) {
status.error(error);
});
});
The posted code does just one http request so there's no need for an array of promises or the invocation of Promise.when(). The rest of what might be happening is obscured by mixing the callback parameters to httpRequest with the promises and the assignment inside the push.
Here's a clarified rewrite:
Parse.Cloud.job("getevent", function(request, status) {
var promises = [];
var params = { url: 'https://api.example.com'};
Parse.Cloud.httpRequest(params).then(function(httpResponse) {
var results = [];
var jsonobj = JSON.parse(httpResponse.text);
for (var i = 0; i < jsonobj.data.length; i++) {
// some code
}
}).then(function() {
status.success("run job");
}, function(error) {
status.error(error);
});
});
But there's a very strong caveat here: this works only if ("// some code") that appears in your original post doesn't itself try to do any asynch work, database or otherwise.
Lets say you do need to do asynch work in that loop. Move that work to a promise-returning function collect those in an array, and then use Promise.when(). e.g....
// return a promise to look up some object, change it and save it...
function findChangeSave(someJSON) {
var query = new Parse.Query("SomeClass");
query.equalTo("someAttribute", someJSON.lookupAttribute);
return query.first().then(function(object) {
object.set("someOtherAttribute", someJSON.otherAttribute);
return object.save();
});
}
Then, in your loop...
var jsonobj = JSON.parse(httpResponse.text);
var promises = [];
for (var i = 0; i < jsonobj.data.length; i++) {
// some code, which is really:
var someJSON = jsonobj.data[i];
promises.push(findChangeSave(someJSON));
}
return Parse.Promise.when(promises);

callback function does not appear to be executing

I have the following code grabbing a JSON object from github and I am tring to add certain parts to an array.
function getTree(hash) {
var pathToTree, returnedJSON;
pathToTree = 'https://api.github.com/repos/myaccount/myrepo/git/trees/' + hash;
$.ajax({
accepts: 'application/vnd.github-blob.raw',
dataType: 'jsonp',
url: pathToTree,
success: function (json) {
returnedJSON = json;
},
error: function (error) {
console.debug(error);
}
});
return returnedJSON;
}
function parseTree(hash) {
var objectedJSON, objectList = [], i, entry;
objectedJSON = getTree(hash, function () {
console.debug(objectedJSON); // this is not appearing in console
for (i = 0; i < objectedJSON.data.tree.length; i += 1) {
entry = objectedJSON.data.tree[i];
console.debug(entry);
if (entry.type === 'blob') {
if (entry.type.slice(-4) === '.svg') { // we only want the svg images not the ignore file and README etc
objectList.append(i.content);
}
} else if (entry.type === 'tree') {
objectList.append(parseTree(getTree(entry.sha)));
}
}
});
return objectList;
}
$(document).ready(function () {
var objects = parseTree('master', function () {
console.debug(objects);
});
});
I have the code retrieving the JSON object fine but I run into trouble when trying to get it parsed (aka pulling out the bits I want). The callbacks I am using do not seem to be going and was wondering if someone could look it over and help me out.
Specifically, can I add a callback to any function I choose? Do I have to do anything to that function?
I have fixed the code to illustrate how you would go about it.
function getTree(hash, cb) {
// notice that I copy the callback and hash references to have access to them in this
// function's closure and any subsequent closures, like the success and error
// callbacks.
var pathToTree, returnedJSON, cb = cb, hash = hash;
pathToTree = 'https://api.github.com/repos/myaccount/myrepo/git/trees/' + hash;
$.ajax({
accepts: 'application/vnd.github-blob.raw',
dataType: 'jsonp',
url: pathToTree,
success: function (json) {
returnedJSON = json;
// if anything was passed, call it.
if (cb) cb(json);
},
error: function (error) {
console.debug(error);
// an error happened, check it out.
throw error;
}
});
return returnedJSON;
}
function parseTree(hash) {
var objectedJSON, objectList = [], i, entry;
objectedJSON = getTree(hash, function (objectedJSON) {
console.debug(objectedJSON); // this is not appearing in console
for (i = 0; i < objectedJSON.data.tree.length; i += 1) {
entry = objectedJSON.data.tree[i];
console.debug(entry);
if (entry.type === 'blob') {
if (entry.type.slice(-4) === '.svg') { // we only want the svg images not the ignore file and README etc
objectList.append(i.content);
}
} else if (entry.type === 'tree') {
objectList.append(parseTree(getTree(entry.sha)));
}
}
});
return objectList;
}
$(document).ready(function () {
var objects = parseTree('master', function () {
console.debug(objects);
});
});
As far as I can see it, you are not passing callback to your functions:
function getTree(hash) {
And you are using like:
objectedJSON = getTree(hash, function () {
Similarly this function does not have callback param:
function parseTree(hash) {
And you are using like:
var objects = parseTree('master', function () {
Modify your functions like this:
function getTree(hash, fn) { ... }
function parseTree(hash, fn) { ... }
And then call fn using fn() when needed.
Add a second parameter o getTree function. Something like
function getTree(hash, callback)
And use a "jsopCallback" parameter in your Ajax options
$.ajax({
...
jsopCallback: callback,
...

Categories