need so help with some JavaScript code that isn't working as expected.
The Application sends files names to the server via Ajax calls to be processed but I want to tell the database that current import has been completed when they are all done. The issue I'm having is the code to update the database with the state runs before any of the ajax calls run.
I have ready in a number of area on the web that you could potentially create a promise array to track that but JavaScript honestly being a weak point of mine, I'm not sure how to I would go about implementing it.
below are my current code snippets:
function that loops through file names:
function importFiles() {
serverImporterInit();
let files = getFiles();
if (files) {
showImportProgressModal(files);
let looper = $.Deferred().resolve();
$.when.apply($, $.map(files, function (file, index) {
let fileStatusElement = document.getElementById('file-' + index + '-status');
let fileErrorElement = document.getElementById('file-' + index + '-error');
looper = looper.then(function () {
setTimeout(function () {
return import_file_request(file, fileStatusElement, fileErrorElement);
}, 2000);
});
return looper;
})).then(function () {
});
}
}
ajax call to the server:
function import_file_request(file, element, errorElement) {
let deferred = $.Deferred();
fileImportStatusInProgress(element);
$.ajax({
type: 'POST',
url: '/importer/manual_import',
data: {'file': file.toString()},
success: function(data) {
fileImportStatusSuccess(element);
deferred.resolve(data);
},
error: function (error) {
fileImportStatusFailed(error, element, errorElement);
deferred.reject(error);
}
});
return deferred.promise();
}
Both of these functions have been derived from other tutorials across the web but I'm not entirely sure if they ever did what I originally intended as I have only just got around to trying to track the completion status due to another requirement.
Any help would be great. Also if there any other details I can include to make this question a little better for other please let me know and I will update accordingly.
Update
I have tried to update the code to use a promise array but still having no luck.
File loop:
const importFiles = async (files) => {
serverImporterInit()
const filesLength = files.length
showImportProgressModal(files);
for (let i = 0; i < filesLength; i++) {
const requests = files.map((file) => {
let fileStatusElement = document.getElementById('file-' + i + '-status');
let fileErrorElement = document.getElementById('file-' + i + '-error');
return import_file_request(file, fileStatusElement, fileErrorElement) // Async function to import file
.then(console.log(file + " successfully imported"))
.catch(e => console.log('Error'))
})
await Promise.all(requests)
.then(serverImporterClose())
.catch(e => console.log(''))
}
}
File import request to sever:
function import_file_request(file, element, errorElement) {
fileImportStatusInProgress(element);
return new Promise((resolve, reject) => {
$.ajax({
type: 'POST',
url: '/importer/manual_import',
data: {'file': file.toString()},
success: function(data) {
fileImportStatusSuccess(element);
resolve();
},
error: function (error) {
fileImportStatusFailed(error, element, errorElement);
reject();
}
});
})
}
You seem to be using jQuery so I try to give a jQuery based solution without the native promise. I think you should either use the native promise combined with the builtin fetch() function and no jQuery or jQuery alone.
The key is to use $.map() to give back an array of promises and then wait for all of them using $.when(). Also it is always important to return the jQuery promise.
function importFiles() {
var files = getFiles();
if (files) {
showImportProgressModal(files);
serverImporterInit();
var promises = $.map(files, function(file, index) {
let fileStatusElement = document.getElementById('file-' + index + '-status');
let fileErrorElement = document.getElementById('file-' + index + '-error');
return import_file_request(file, fileStatusElement, fileErrorElement)
})
$.when.apply($, promises).then(function(results) {
serverImporterClose();
results.forEach(function(result) {
if (result) {
console.log("yay, success");
}
else {
console.log("failed");
}
})
hideImportProgressModal(files);
});
}
}
function import_file_request(file, element, errorElement) {
fileImportStatusInProgress(element);
// MUST return the promise here
return $.ajax({
type: 'POST',
url: '/importer/manual_import',
data: {'file': file.toString()},
success: function(data) {
fileImportStatusSuccess(element);
return true;
},
error: function (error) {
fileImportStatusFailed(error, element, errorElement);
return false;
}
});
}
Related
For some reason my async call is not working as expected. Here's what I'm trying to do:
Make several ajax calls in a loop
On success, push some API data to a global array
Use that array in another function (e.g print it out)
var authcodes = ["E06000001","E06000002","E06000003"];
var dict = [];
async function ajaxCall() {
for(var i=0;i<authcodes.length;i++){
$.ajax({
type: "GET",
url: 'https://api.coronavirus.data.gov.uk/v1/data?filters=areaCode=' + authcodes[i] +'&structure={"areaCode":"areaCode","cumCasesByPublishDate":"cumCasesByPublishDate"}',
dataType: "json",
success: function(data) {
dict.push(data.data[0].cumCasesByPublishDate);
}
});
} return dict;
}
async function printArr () {
const someApiRes = await ajaxCall()
console.log(someApiRes[1]); //this doesn't work
console.log(dict[1]); //this doesn't work
}
printArr();
Here is the JSFiddle with code commented: https://jsfiddle.net/gjv9hrpo/1/
I understand that the printArr() function has to be ran after the array is populated due to the async nature which I hoped the await would solve. Am I using it wrong?
Thanks.
Use promise.All()
async function ajaxCall() {
var promises = [];
for(var i=0;i<authcodes.length;i++){
promises.push($.ajax({
type: "GET",
url: 'https://api.coronavirus.data.gov.uk/v1/data?filters=areaCode=' + authcodes[i] +'&structure={"areaCode":"areaCode","cumCasesByPublishDate":"cumCasesByPublishDate"}',
dataType: "json",
success: function(data) {
dict.push(data.data[0].cumCasesByPublishDate);
}
}));
}
await Promise.all(promises);
return dict;
}
It might make more sense to do this with promises.
Promise.all lets you know when either all input promises have fulfilled or when one of them rejects.
var authcodes = ["E06000001", "E06000002", "E06000003"];
function ajaxCalls() {
return Promise.all(
authcodes.map((code) => {
return new Promise(async (resolve) => {
const response = await fetch(
`https://api.coronavirus.data.gov.uk/v1/data?filters=areaCode=${code}&structure={"areaCode":"areaCode","cumCasesByPublishDate":"cumCasesByPublishDate"}`
)
const json = await response.json();
resolve(json.data[0].cumCasesByPublishDate);
});
})
);
}
function printArr() {
ajaxCalls().then((values) => {
console.log(values);
}).catch(e => {
console.error(e)
});
}
printArr();
Promise.allSettled gives you a signal when all the input promises are settled, which means they’re either fulfilled or rejected. This is useful in cases where you don’t care about the state of the promise, you just want to know when the work is done, regardless of whether it was successful.
var authcodes = ["E06000001", "E06000002", "E06000003"];
function ajaxCalls() {
return Promise.allSettled(
authcodes.map((code, i) => {
return new Promise(async (resolve, reject) => {
if (i ===0 ) reject('something went wrong')
const response = await fetch(
`https://api.coronavirus.data.gov.uk/v1/data?filters=areaCode=${code}&structure={"areaCode":"areaCode","cumCasesByPublishDate":"cumCasesByPublishDate"}`
);
const json = await response.json();
resolve(json.data[0].cumCasesByPublishDate);
});
})
);
}
async function printArr() {
const results = await ajaxCalls();
console.log(results.map((result) => result.value || result.reason));
}
printArr();
I recently met a problem with promise Chain in javascript, specifically in Vue.js.
This is my code, I have a addItem function that insert an item in database. I want to have this function run it insert things in database then use getItems function to renew all the data. However, what I find out is that this function will run the things in the .then first then insert the item in the database at last. This caused my code to break. If any of you can help me that will be great!
addItem: function() {
this.$store.dispatch('addJournal',{
journal: this.text,
page: this.maxPage + 1, // increase the page number
}).then(response => {
this.text = ""; // set the input to empty
this.getItems(); // get all the data from database
this.setMaxPage(); // reset the max size
this.currentPage = this.maxPage; // go to the max page
this.option = "current";// set back to current
}).catch(err => {
});
},
this is other corresponding code
getItems: function() {
this.pages = [];
var tempArray = [];
tempArray = this.$store.getters.feed;
for (var index = 0; index < tempArray.length; ++index) {
let page = {text:tempArray[index].journal,
pageNumber:tempArray[index].page};
this.pages.push(page);
}
},
this is the addJournal function in store.js
addJournal(context,journal) {
console.log("this is for users", context.state.user.id)
axios.post("/api/users/" + context.state.user.id + "/journals",journal).then(response => {
return context.dispatch('getFeed');
}).catch(err => {
console.log("addJournal failed:",err);
});
context.dispatch('getFeed');
}
You need to convert addJournal into something that returns a promise, so that it can be consumed with then:
addJournal(context, journal) {
console.log("this is for users", context.state.user.id)
context.dispatch('getFeed');
return axios.post("/api/users/" + context.state.user.id + "/journals", journal).then(response => {
return context.dispatch('getFeed');
}).catch(err => {
console.log("addJournal failed:", err);
});
}
Not sure what context.dispatch('getFeed'); does, but since posting is asynchronous, there shouldn't be anything wrong with moving it above the axios.post line. axios.post returns a promise already, so you just need to return it.
.then() works on promise
for this.$store.dispatch('addJournal').then(...) to work as expected, addJournal should be a promise.
Here's how
addJournal(context, journal) {
return new Promise((resolve, reject) => {
axios
.post("/api/users/" + context.state.user.id + "/journals", journal)
.then(response => {
context.dispatch("getFeed");
resolve();
})
.catch(err => {
console.log("addJournal failed:", err);
reject(err);
});
});
}
I have an API call with a forEach loop which I need to be finished before another function is called. It looks like this:
var getTypes = function() {
var stations = [];
stationservice.getCount('/stations')
.then(succCB, errorCB);
function succCB(data) {
data.data.forEach(function(station) {
stations.push({
id: station._id,
})
})
};
// This should only be called once the forEach Loop is done
processStations(stations);
}
I can't find an understandable example of how I can make sure the processStations() gets called once the loop is done. How can I create a promise for this such that it does what I want to achieve?
As soon as you use promises you have to chain everything that depends on that promise (or use await and async if your environment supports it):
function getTypes() {
return stationservice.getCount('/stations')
.then(function(data) {
var stations = [];
data.data.forEach(function(station) {
stations.push({
id: station._id,
})
})
return stations;
})
.then(processStations);
}
And you should return the Promise chain from your getTypes at least if the getTypes should return something that depends on the stationservice.getCount.
Instead of the forEach you might want to use map because this is what you actually do:
function getTypes() {
return stationservice.getCount('/stations')
.then(function(data) {
return data.data.map(function(station) {
return {
id: station._id,
};
})
})
.then(processStations);
}
If you want a "modern code" answer
var getTypes = function() {
return stationservice.getCount('/stations')
.then(data => data.data.map(({_id: id}) =>({id})))
.then(processStations);
}
this is equal to
var getTypes = function getTypes() {
return stationservice.getCount('/stations').then(function (data) {
return data.data.map(function (_ref) {
return { id: _ref._id };
});
}).then(processStations);
};
Though, since the map isn't asynchronous at all
const getTypes = () => stationservice.getCount('/stations').then(data => processStations(data.data.map(({_id: id}) =>({id}))));
is just fine - and in pre modern browser
var getTypes = function getTypes() {
return stationservice.getCount('/stations').then(function (data) {
return processStations(data.data.map(function (_ref) {
return { id: _ref._id };
}));
});
};
Using Async library
async.forEach(data.data, function (item, callback){
stations.push({
id: item._id,
})
callback();
}, function(err) {
processStations(stations);
});
I have the following piece of code right now:
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);
recordPerfMetrics: function(url) {
var self = this;
var perf, loadTime, domInteractive, firstPaint;
var perfData = {};
readFile('urls.txt', 'UTF-8').then(function (urls, err) {
if (err) {
return console.log(err);
}
var urls = urls.split("\n");
urls.shift();
urls.forEach(function(url) {
console.log(url);
self.getStats(url).then(function(data) {
data = data[0];
loadTime = (data.loadEventEnd - data.navigationStart)/1000 + ' sec';
firstPaint = data.firstPaint;
domInteractive = (data.domInteractive - data.navigationStart)/1000 + ' sec';
perfData = {
'URL' : url,
'firstPaint' : firstPaint,
'loadTime' : loadTime,
'domInteractive' : domInteractive
};
console.log(perfData);
}).catch(function(error) {
console.log(error);
});
});
// console.log(colors.magenta("Starting to record performance metrics for " + url));
// this.storePerfMetrics();
});
},
getStats: function(url) {
return new Promise(function(resolve, reject){
console.log("Getting data for url: ",url);
browserPerf(url, function(error, data) {
console.log("inside browserPerf", url);
if (!error) {
resolve(data);
} else {
reject(error);
}
}, {
selenium: 'http://localhost:4444/wd/hub',
browsers: ['chrome']
});
});
},
This is basically reading urls from a file and then calling a function browserPerf whose data being returned is in a callback function.
The console.log("Getting data for url: ",url); is in the same order as the urls that are stored in the file,
but the console.log("inside browserPerf", url); is not conjunction as the same and as expected.
I expect the order of the urls to be:
console.log(url);
console.log("Getting data for url: ",url);
console.log("inside browserPerf", url);
But for reason only the first two are executed in order but the third one is fired randomly after all are being read.
Any idea what i am doing wrong here?
Since you are using Bluebird, you can replace your .forEach() loop with Promise.mapSeries() and it will sequentially walk through your array waiting for each async operation to finish before doing the next one. The result will be a promise who's resolved value is an array of results. You also should stop declaring local variables in a higher scope when you have async operations involved. Declare them in the nearest scope practical which, in this case is the scope in which they are used.
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);
recordPerfMetrics: function() {
var self = this;
return readFile('urls.txt', 'UTF-8').then(function (urls) {
var urls = urls.split("\n");
urls.shift();
return Promise.mapSeries(urls, function(url) {
console.log(url);
return self.getStats(url).then(function(data) {
data = data[0];
let loadTime = (data.loadEventEnd - data.navigationStart)/1000 + ' sec';
let firstPaint = data.firstPaint;
let domInteractive = (data.domInteractive - data.navigationStart)/1000 + ' sec';
let perfData = {
'URL' : url,
'firstPaint' : firstPaint,
'loadTime' : loadTime,
'domInteractive' : domInteractive
};
console.log(perfData);
return perfData;
}).catch(function(error) {
console.log(error);
throw error; // keep the promise rejected
});
});
// console.log(colors.magenta("Starting to record performance metrics for " + url));
// this.storePerfMetrics();
});
},
getStats: function(url) {
return new Promise(function(resolve, reject){
console.log("Getting data for url: ",url);
browserPerf(url, function(error, data) {
console.log("inside browserPerf", url);
if (!error) {
resolve(data);
} else {
reject(error);
}
}, {
selenium: 'http://localhost:4444/wd/hub',
browsers: ['chrome']
});
});
},
You would use this like this:
obj.recordPerfMetrics().then(function(results) {
// process results array here (array of perfData objects)
}).catch(function(err) {
// error here
});
Summary of changes:
Return promise from recordPefMetrics so caller can get data
Use Promise.mapSeries() instead of .forEach() for sequential async operations.
Return promise from Promise.mapSeries() so it is chained with prior promise.
Move variable declarations into local scope so there is no change of different async operations stomping on shared variables.
Rethrow .catch() error after logging so the reject propagates
return perfData so it becomes the resolved value and is available in results array.
This code needs to declare a function (preferably anonymous) inside the array
({argObj}) => {console.log(start);}
and define it later outside the request = (function() {...}()); IIF.
request = (function () {
const pathAfter = {
start: ['homePage', 'GET', ({argObj}) => {console.log(`start`);}]
};
return {
go: (argObj) => {
if(!pathAfter[argObj.pathAfter]) return;
const path = pathAfter[argObj.pathAfter][0];
const method = pathAfter[argObj.pathAfter][1];
const url = data.domain + path + data.ext;
HTTP.call(method, url, (error, response) => {
if (error) {
console.log('error '+error);
} else {
pathAfter[path][2]({response: response}); // <---- calls relevant method
request.go({pathAfter: path});
}
});
return true; // if rms seccessful
}
};
}());
// function definition goes here...
I am not sure how to do this. Thanks
I am not entirely clear on what you are trying to acheive and how strict the requirements are, but one option might be to give the request object the ability to add/extend the handlers in pathAfter:
request = (function () {
const pathAfter = {
start: ['homePage', 'GET', ({argObj}) => {console.log('start');}]
};
return {
go: (argObj) => {
if(!pathAfter[argObj.pathAfter]) return;
const path = pathAfter[argObj.pathAfter][0];
const method = pathAfter[argObj.pathAfter][1];
const url = data.domain + path + data.ext;
HTTP.call(method, url, (error, response) => {
if (error) {
console.log('error '+error);
} else {
pathAfter[path][2]({response: response}); // <---- calls relevant method
request.go({pathAfter: path});
}
});
return true; // if rms seccessful
},
registerPathHandler: (handlerName,handler)=> {
pathAfter[handlerName] = handler;
}
};
}());
request.registerPathHandler('test', ['testPage', 'GET', ({argObj}) => {console.log('test');}]);
This will add a named handler, and could be used to add the start handler as well. If the start handler really needs to be hard-coded inside, then the code above could be modified to just replace the array element instead.