How to write this chain without losing the promise? - javascript

I have this (simplified here) code when the "reading" function calls itself many times (it is actually reading a file by chunks). The problem is that by doing so (this is what I think), the chain is lost and the "then" of the upload() function is never reached.
In other words, the 'hola' text is never displayed in the console. What is the proper way to write this?
Thanks,
https://jsfiddle.net/yewdmh2o/1/
reading = function() {
start++;
upload();
};
var start = 0;
var end = 5;
var myArray = [3,5,7];
myArray.reduce(function(promise, item) {
return upload()
.then(function(){
console.log('hola');
})
})
function upload() {
if (start < end) {
console.log('doing')
reading();
} else {
console.log('finished')
return Promise.resolve();
}
}
EDIT:
Solved with the help of #Bergi, by always returning a promise it can keep the chaining process and give the desired outcome, like this:
reading = function() {
start++;
return upload();
};
var start = 0;
var end = 5;
var myArray = [3,5,7];
myArray.reduce(function(promise, item) {
return upload()
.then(function(){
console.log('hola');
})
})
function upload() {
if (start < end) {
console.log('doing')
return reading();
} else {
console.log('finished')
return Promise.resolve();
}
}

Related

for loop is giving partial output in async function

I am getting partial output for the function call. Could anyone tell what I am doing wrong
Data in database in JSON format in MongoDB
DATA IN MONGODB
Function code
async function isTeamNameExists(department, teamID) {
var store = await new Promise(async (resolve, reject) => {
//database read function
db.readCollection(collection_name, (status, data) => {
if (status) {
var teamname = new SB.ListofNames();
teamname.listofboards = data;
var send = teamname;
console.log("send----->", send);
//checking for teamname exists?
for (var boardindex = 0; boardindex < send.listofboards.length; boardindex++) {
var tn = send.listofboards[boardindex];
if (tn.department == department && tn.teamId == teamId) {
resolve(tn);
} else {
resolve(null);
}
}
reject(null);
}
});
});
console.log("store---->", store); // the function return value =store
return (store);
//resolve(store); does not work
}
Function call 1
var output1 = isTeamNameExists(D1,11);
Result-> {department:D1 , teamId:11} // exists ->returns the particular json data of teamId=11
Function call 2
var output2= isTeamNameExists(D2,22);
Result-> null // it should not return null,but it should return {department:D2 , teamId:22}
Function call 3
var output3= isTeamNameExists(D78b,22000211);
Result-> null // it should return null. correct output
> So I am getting partial output. could you tell me what I am doing wrong? I am new to javascript, learning something everyday.
Your function is resolving the moment the first run in the for (var boardindex = 0; boardindex < send.listofboards.length; boardindex++) loop is done because of the else condition.
You want the loop to go through all the items and reject if no entry is found (which you are doing correctly.)
Remove the below part and you'll be good.
} else {
resolve(null);
//resolve(store); does not work will not work because isTeamNameExists function is not a promise.

issue with pushing data into a new array while in a promise chain

I'm having trouble figuring out why my data is not being push into my new array, "results". newArr[0].mscd.g[i] is a list of several objects.
var axios = require('axios');
var moment = require('moment');
var _ = require('lodash');
var getData = function() {
return getNBASchedule().then(function(payload) {
return filterByMonth('January', payload);
}).then(function(result) {
return result
});
}
....
getData grabs the data from baseURL and returns a list of objects.
var getMonthlySchedule = function(data){
var results = [];
var newArr = data.slice(0, data.length);
for (var i = 0; i <= newArr[0].mscd.g.length; i++) {
if (newArr[0].mscd.g[i].v.tid === 1610612744 || newArr[0].mscd.g[i].h.tid === 1610612744) {
results.push(newArr[0].mscd.g[i]); <---- //does not seem to work
// however if I were to console.log(newArr[0].mscd.g[i],
// I would see the list of objects)
}
}
return results; <-- //when i console at this point here, it is blank
};
var getSchedule = function () {
return getData().then(function(pl) {
return getMonthlySchedule(pl)
})
};
var monthlyResults = function() {
return getSchedule().then(function(r) {
console.log("result", r)
return r
});
};
monthlyResults();
You don't know when getSchedule() is done unless you use a .then() handler on it.
getSchedule().then(function(data) {
// in here results are valid
});
// here results are not yet valid
You are probably trying to look at your higher scoped results BEFORE the async operation has finished. You HAVE to use .then() so you know when the operation is done and the data is valid.
Your code should simplify as follows :
var getData = function() {
return getNBASchedule().then(function(payload) {
return filterByMonth('January', payload);
});
}
var getMonthlySchedule = function(data) {
return data[0].mscd.g.filter(function(item) {
return item.v.tid === 1610612744 || item.h.tid === 1610612744;
});
};
var monthlyResults = function() {
return getData()
.then(getMonthlySchedule)
.then(function(r) {
console.log('result', r);
return r;
});
};
monthlyResults();
This may fix the problem. If not, then :
Check the filter test. Maybe those .tid properties are String, not Number?
Check that data[0].mscd.g is the right thing to filter.

Why can´t I call a synchronous function inside a async promise?

I´m using express/ node, mysql and bluebird.
I´m performing three async database queries using Promise.all(). After these were performed I have to do some calculation.
If I do this I can call the function getDateRange() successfully, but this leads me to the problem, that I have trouble doing some error handling.
return Promise.all([departmentDatabase.getVacation(departmentID), departmentDatabase.countUser(departmentID), departmentDatabase.blockedDaysOfResponsible(departmentID)])
.then(departmentData => [departmentData, departmentData[0].map(entry => this.getDateRange(new Date(entry.dateFrom), new Date(entry.dateTo)))] )
.spread(function(departmentData, dateRange){
var mergedDateRange = [].concat.apply([], dateRange);
var counts = {};
mergedDateRange.forEach(function(x) { counts[x] = (counts[x] || 0)+1; }); //Zähle doppelt vorkommende Daten
var departmentMinStock = departmentData[0][0].departmentMinStock;
var blockedDays = [];
for (var p in counts) {
if( counts.hasOwnProperty(p) ) {
if( (parseInt(departmentData[1][0].amount) - parseInt(counts[p])) <= parseInt(departmentMinStock)) {
blockedDays.push(p);
}
}
}
return [{"vacationRequest": departmentData[0], "blockedDays": blockedDays}, departmentData[2]];
})
.catch(err => {
// ...do something with it...
// If you want to propagate it:
return Promise.reject(err);
// Or you can do:
// throw err;
});
In case that departmentData is undefined, which can happen quit often, I have no real option (that I know) to stop all following .then() calls and just return an empty array. I could return an empty array, but then I would have to do the check in every .then() is have.
So I thought, because I use Promise.all() I don´t really have following async operation, therefore I could just do all my logic inside the callback of .all().
But if I try to do it this way:
return Promise.all([departmentDatabase.getVacation(departmentID), departmentDatabase.countUser(departmentID), departmentDatabase.blockedDaysOfResponsible(departmentID)])
.then(function(data){
var d = [];
for(var i = 0; data[0].length > i; i++){
//console.log(this.getDateRange(new Date(data[0][0].dateFrom), new Date(data[0][0].dateTo)));
//console.log(data[0][i].dateFrom);
//console.log(data[0][i].dateTo);
var x = this.getDateRange(data[0][i].dateFrom, data[0][i].dateTo);
console.log(x);
// d.push(this.getDateRange(new Date(data[0][0].dateFrom), new Date(data[0][0].dateTo)));
}
return 1;
})
It results in an error TypeError: Cannot read property 'getDateRange' of undefined. But console.log(data[0][i].dateFrom); has a value I have checked that.
This is my getDateRange function:
getDateRange(startDate, stopDate) {
var dateArray = [];
var currentDate = moment(startDate);
while (currentDate <= stopDate) {
dateArray.push(moment(currentDate).format('YYYY-MM-DD'))
currentDate = moment(currentDate).add(1, 'days');
}
return dateArray;
}
Can someone exlpain me why this is happening? And best would be an example on how to do it right in this case?

Best way to write loops with promises (ctx.sync) in JavaScript API for Office

There are many threads that discuss about guaranteeing execution order of promises in loops. I would like to know what is the best practice in JavaScript API for Office Add-ins. Most of the time, the promise in question is ctx.sync().
Here is a snippet to print the address of a list of Excel ranges one by one. The test shows that it respects well the order of Excel ranges. But the question is whether and how to guarantee the execution order?
function loadAll () {
var ranges = ["A:A", "B:B", "C:C", "D:D", "E:E"];
var sheet = "Sheet1";
for (var i = 0; i < ranges.length; i++) {
loadRange(ranges[i], sheet);
}
}
function loadRange (range, sheet) {
Excel.run(function (ctx) {
var r = ctx.workbook.worksheets.getItem(sheet).getRange(range);
r.load('address');
return ctx.sync().then(function() {
console.log(r.address);
});
});
}
Could anyone help?
Because Excel.run returns a Promise, you can chain it with a .then and guarantee order. I.e.,
Excel.run(function(ctx) { ... return ctx.sync(); ... })
.then(function() {
return Excel.run(function(ctx) { ... return ctx.sync(); ... })
})
.then(function() {
return Excel.run(function(ctx) { ... return ctx.sync(); ... })
});
That being said... this would be pretty dang inefficient. A much better approach would be to load all the objects you need in one batch, creating only one network roundtrip (especially important with Excel Online... but noticeable even on the Desktop):
function loadAll () {
Excel.run(function(ctx) {
var ranges = ["A:A", "B:B", "C:C", "D:D", "E:E"];
var sheet = "Sheet1";
var loadedRanges = [];
for (var i = 0; i < ranges.length; i++) {
var r = ctx.workbook.worksheets.getItem(sheet).getRange(ranges[i]);
r.load('address');
loadedRange.push(r);
}
return ctx.sync()
.then(function() {
for (var i = 0; i < loadedRanges.length; i++) {
console.log(loadedRanges[i].address);
}
});
});
}
UPDATE
If, as per comment, you do end up needing to do separate tasks that depend on each other and that each require a roundtrip, and hence do need to be sequenced via chaining Excel.run, I would recommend something as follows:
function loadAll () {
var ranges = ["A:A", "B:B", "C:C", "D:D", "E:E"];
var sheet = "Sheet1";
// Create a starter promise object
var promise = new OfficeExtension.Promise(function(resolve, reject) { resolve (null); });
for (var i = 0; i < ranges.length; i++) {
// Create a closure over i, since it's used inside a function that won't be immediately executed.
(function(i) {
// Chain the promise by appending to it:
promise = promise.then(function() {
return loadRange(ranges[i], sheet);
})
})(i);
}
}
function loadRange (range, sheet) {
return Excel.run(function (ctx) {
var r = ctx.workbook.worksheets.getItem(sheet).getRange(range);
r.load('address');
return ctx.sync().then(function() {
console.log(r.address);
});
});
}
~ Michael Zlatkovsky, developer on Office Extensibility team, MSFT
The update of #Michael Zlatkovsky solves the problem of appending promises perfectly.
The complement added by #SoftTimur allows to wait for all promises to be done before doing a .then(), which is also very convenient !
My only remark about these posts would be if ANY promises throws an error, the other appended promises stop being treated.
In my case, the scenario is a little different. Just to clarify:
Excel.run(function(context){
return runWorkbook(context, context.workbook)
.then(function(){ var cool = "all promises worked !" }
.catch(function(error)) { var bad = "do not want to be here :(" });
}
function runWorkbook(context, workbook){
const sheets = workbook.worksheets;
sheets.load("$none");
return context.sync().then(function(){
let promise = new window.OfficeExtension.Promise(function(resolve, reject) { resolve(null); });
sheets.items.forEach(function(ws) {
promise = promise.then(function() {
return makeWorkOnWorksheet(ws)
.then(context.sync())
.catch(function(error)){
// DO NOTHING BUT CAN NOT THROW ERROR OTHERWISE IT BREAKS THE NEXT APPENDED PROMISES
});
}
return promise;
}
}
This solution works.. (catch the error as in comment and doing nothing with it)
I don't like this solution but this is the only way I found to allow all appended promises to be done.
If somebody has a better idea, it's welcomed ;)
Cheers,

Javascript for loop Promises

I have an array of urls like this
var urls = ["www.google.com", "www.yahoo.com"];
And I want to loop though the urls and perform an async task inside the loop and not move on to the next item until the async task has finished. I know you can do this with promises but I have having some trouble with it. Here what I have
var xmlReader = require('cloud/xmlreader.js');
function readResponse_async(xlmString) {
var promise = new Parse.Promise();
xmlReader.read(xlmString, function (err, res) {
if(err) {
promise.reject(err);
} else {
promise.resolve(res);
}
});
return promise;
}
for (i = 0; i < urls.length; i++) {
Parse.Cloud.httpRequest({
url: unionUrls[i],
}).then(function(httpResponse) {
try {
// console.log(httpResponse.text)
return readResponse_async(httpResponse.text)
} catch (e) {console.log(e)}
}
But right now it doesn't wait for the readResponse_async to finish, how can I have it wait for that?
Thanks
EDIT
After reading the response I make a save to my database and I have another array like this
var location = ['USA', 'England'];
And I make the save like this
function saveLoc_async(data, location) {
var i3, i4, i5, m,
TestItem = Parse.Object.extend("TestItem"),//can be reused within the loops?
promise = Parse.Promise.as();//resolved promise to start a long .then() chain
for (i3 = 0; i3 < data.count(); i3++) {
(function(testItem) {
testItem.set("item", data.at(i));
testItem.set("location", location);
//build the .then() chain
promise = promise.then(function() {
return testItem.save();
});
})(new TestItem());
//************************
//CALL retry(); here?
//**************************
}
Because with your answer I have
function retry() {
if (urlsUnion.length > 0) {
var nextUrl = urlsUnion.pop();
//********** ADDED LINE
var nextLoc = location.pop();
Parse.Cloud.httpRequest({
url: nextUrl,
}).then(function(httpResponse) {
xmlReader.read(httpResponse.text, function (err, res) {
if(err) {
// show an error
} else {
//********** ADDED LINE
saveLoc_async(res, nextLoc);
retry();
}
});
});
}
}
SO where should retry(); go because right now with the save sometimes it puts the second location with one of the first items url? why would that happen?
I did something similar to this for an animation.
var actions = [drawXXX, fadeOutYYY, drawXYZ];
this.startAnimation = function () {
actions.reduce(function (previousAction, nextAction) {
return previousAction.then(nextAction)
}, $.when());
}
Your code fires both urls immediately, and does not wait in-between.
What you would have to do is to remove the first url from the array and fire it. In the 'then' branch check if you still have url's in the array and repeat.
Like this (untested, edited to make the code clean again):
var xmlReader = require('cloud/xmlreader.js');
function readResponse_async(xlmString) {
xmlReader.read(xlmString, function (err, res) {
if(err) {
// show an error
} else {
readFirstUrl();
}
});
}
function readFirstUrl() {
if (urlsUnion.length == 0) {
return;
}
var url = urlsUnion.pop();
Parse.Cloud.httpRequest({
url: url,
}).then(function(httpResponse) {
readResponse_async(httpResponse.text);
});
}
readFirstUrl();
Not sure I understand your use of unionUrls array, but if you have your URL's in a urls array, I think this is pretty clean:
function getUrl(url) {
return Parse.Cloud.httpRequest(url)
.then( function(httpResponse) {
return readResponse_async(httpResponse.text);
});
}
urls.reduce( function(prev, url) {
return prev ? prev.then( function() { getUrl(url); }) : getUrl(url);
}, null);

Categories

Resources