missing timing from promised value - javascript

So I am using Forge with View API to analyze all parts from a model which contain concrete and hide everything that is not concrete. The problem is that the properties for checking concrete are called from a DB which requires me to make it a promise. Checking for concrete is working as expected and then the problem starts. I return the Ids containing concrete, but my function which is supposed to hide it uses the Ids before the promise is resolved, so the array is empty.
console.log logs it as expected but everything else misses the timing.
My code:
getProperties(dbId) {
return new Promise((resolve, reject) => {
this.gui.getProperties(
dbId,
args => {
resolve(args.properties)
},
reject
)
})
}
async getConcreteIds() {
let wallfloorids = this.getWallIds().concat(this.getFloorIds());
let concreteIds = [];
for (let id of wallfloorids) {
let p1 = this.view.getProperties(id);
p1.then(props => {
for (let prop of props) {
if (prop.displayCategory === "Materialien und Oberflächen" && prop.displayValue.contains("Concrete")) {
concreteIds.push(id);
}
}
}).catch(() => {
});
}
return new Promise((resolve, reject) => {
try {
resolve(concreteIds)
} catch (e) {
console.log("Err", reject)
}
})
}
async onlyConcrete() {
this.getConcreteIds().then(concrete => {
debugger;
this.viewer.isolateById(concrete)
});
}

Map an array of promises for your loop and use Promise.all() to resolve after all the promises in loop resolve
Something like:
async getConcreteIds() {
let wallfloorids = this.getWallIds().concat(this.getFloorIds());
let concreteIds = [];
let promises = wallfloorids.map(id => {
let p1 = this.view.getProperties(id);
return p1.then(props => {
for (let prop of props) {
if (prop.displayCategory === "Materialien und Oberflächen" && prop.displayValue.contains("Concrete")) {
concreteIds.push(id);
}
}
});
});
return Promise.all(promises)
.then(_ => concreteIds)
.catch(err => console.log("Err", err))
}

Related

writeFile does not wait for variable to be instantiated

I'm new to node.js and javascript in general but I am having issues understanding why the writeFile function is writing a blank file. I think the for loop should be a Promise but I am not sure how to write it.
const removeProviders = function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = '';
for (let providerId in providerArray) {
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
fs.writeFile('C:/path/to/file.txt', importFile);
You can collect all the promises from the for-loop into an Array and then pass them into Promise.all() which will resolve only after all of them resolved. If one of the promises are rejected, it will reject as well:
const promises = providerArray.map((item) => {
return getProvider(item)
.then((response) => {
let providerInfo = response.data;
return providerInfo;
})
.then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
});
});
Promise.all(promises).then(() => {
fs.writeFile('C:/path/to/file.txt', importFile, err => {
if (err) {
console.error(err);
}
});
});
While doing this you could also get rid of the importFile variable and collect directly the results of your promises. Promise.all().then(results => {}) will then give you an array of all results. Thus no need for an updatable variable.
The below approach may be useful.
I don't know your getProvider return Promise. you can set promise in getProvider method
const getProviderValue = async function(providerArray) {
return new Promise((resolve, reject) {
let importFile = '';
for (let providerId in providerArray) {
await getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
resolve(importFile)
})
}
const removeProviders = async function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = await getProviderValue(providerArray)
fs.writeFile('C:/path/to/file.txt', importFile);
})
}
Your for loop does not wait for the promises to resolve. A better way to approach this problem would be to use reduce.
providerArray.reduce(
(p, _, i) => {
p.then(_ => new Promise(resolve =>
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += entry;
resolve();
}))
);
}
, Promise.resolve() );
You can also use Promise.all but the out data may not be in the order that you expect if you append to the importFile variable.

Firestore query for loop with multiple values

I am attempting to retrieve a number of Firestore documents using data held in a string. The idea is that for each value in the array, i'd use Firestore query to retrieve the document matching that query and push it to another array. I am having a few issues achieving this. So far i've tried:
exports.findMultipleItems = functions.https.onRequest((request, response) => {
var list = ["item1", "item2", "item3", "item4"];
var outputList = [];
for (var i = 0; i < list.length; i++) {
console.log("Current item: " + list[i]);
let queryRef = db.collection("items").where('listedItems', 'array-contains', list[i]).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
}
snapshot.forEach(doc => {
outputList.push(doc.data());
});
return;
})
.catch(err => {
console.log('Error getting documents', err);
});
}
response.send(JSON.stringify(outputList));
});
I'm not entirely sure but i think one of the issues is that the for loop is being completed before the queries have a chance to finish.
P.s - this is being ran through Cloud Functions using Admin SDK.
Your queryRef is not actually a reference. It's a promise that resolves after your get/then/catch have finished. You need to use these promises to determine when they're all complete. The array will be populated only after they are all complete, and only then is it safe to send the response using that array.
Collect all the promises into an array, and use Promise.all() to get a new promise that resolves after they're all complete:
exports.findMultipleItems = functions.https.onRequest((request, response) => {
var list = ["item1", "item2", "item3", "item4"];
var outputList = [];
const promises = [];
for (var i = 0; i < list.length; i++) {
console.log("Current item: " + list[i]);
let promise = db.collection("items").where('listedItems', 'array-contains', list[i]).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
}
snapshot.forEach(doc => {
outputList.push(doc.data());
});
return;
})
.catch(err => {
console.log('Error getting documents', err);
});
promises.push(promise);
}
Promise.all(promises).then(() => {
response.send(JSON.stringify(outputList));
}
.catch(err => {
response.status(500);
})
});
You might want to use these tutorials to better understand how to deal with promises in Cloud Functions:
https://firebase.google.com/docs/functions/video-series/
You need to look into promises, Never called a promise in a loop like this. First, you need to chunk your code which returns result from the DB(asynchronously) and uses Promise.all() to handle multiple promises.
utils.getData = async (item) => {
try {
const result = await db.collection("items").where('listedItems', 'array-contains', item).get();
return result;
} catch (err) {
throw err;
}
};
utils.getDataFromDB = async () => {
try {
const list = ["item1", "item2", "item3", "item4"];
const outputList = [];
const promises = [];
for (var i = 0; i < list.length; i++) {
console.log("Current item: " + list[i]);
const element = list[i];
promises.push(utils.getData(elem));
}
const result = await Promise.all(promises);
result.forEach((r) => {
if (r.empty) {
console.log('No matching documents.');
} else {
snapshot.forEach(doc => {
outputList.push(doc.data());
});
}
});
return outputList;
} catch (err) {
throw err;
}
}
module.exports = utils;
Here is my attempt at fully idiomatic solution. It needs no intermediate variables (no race conditions possible) and it separates concerns nicely.
function data_for_snapshot( snapshot ) {
if ( snapshot && !snapshot.empty )
return snapshot.map( doc => doc.data() );
return [];
}
function query_data( search ) {
return new Promise( (resolve, reject) => {
db
.collection("items")
.where('listedItems', 'array-contains', search)
.get()
.then( snapshot => resolve(snapshot) )
.catch( resolve( [] ) );
});
}
function get_data( items )
{
return new Promise( (resolve) => {
Promise
.all( items.map( item => query_data(item) ) )
.then( (snapshots) => {
resolve( snapshots.flatMap(
snapshot => data_for_snapshot(snapshot)
));
});
});
}
get_data( ["item1", "item2", "item3", "item4"] ).then( function(data) {
console.log( JSON.stringify(data) );
});
I used a simple mockup for my testing, as i don't have access to that particular database. But it should work.
function query_data( search ) {
return new Promise( (resolve, reject) => {
setTimeout( () => {
resolve([{
data: function() { return search.toUpperCase() },
empty: false
}])
});
});
}

Promise return multiple values

I use the following code to return promise which is working OK.
The promise return the data value
run: () => {
return new Promise((resolve, reject) => {
....
}).then((data) => {
let loginApi = data[0]
let test = 1;
}).catch((err) => {
if (err.statusCode === 302) {
var data = url.parse(err.response.headers.location, true)
resolve(data )
}
})
});
I call it
module.run()
.then((data) => {
And I was able to get the data.
now I want to return also value test in the resolve, how should I do it?
I try to add it like this
resolve({data,test});
resolve([data,test]);
with call like
module.run()
.then({data,test}) => {
without success(test is empty), I read about spread but this is the only option?
I use ES6 with bluebird latest version
If you are using promise chain, in promise chain you have then->then->catch->... format. Always return Promise.resolve or Promise.reject. Promise.resolve will give success result for next then block and Promise.reject will go to next catch block.
var module = {
run: () => {
return new Promise((resolve, reject) => {
// ....
resolve('promise resolved')
}).then((data) => {
let loginApi = data[0]
let test = 1;
return Promise.resolve({data,test})
}).catch((err) => {
if (err.statusCode === 302) {
var data = url.parse(err.response.headers.location, true)
return Promise.resolve({data, test});
}
return Promise.reject(err);
})
}
};
module.run().then(({data, test}) => {
console.log(data, test);
})

Fetch multiple promises, return only one

I have a list of urls I wish to fetch. All of these urls returns a json object with a property valid. But only one of the fetch promises has the magic valid property to true.
I have tried various combinations of url.forEach(...) and Promises.all([urls]).then(...). Currently my setup is:
const urls = [
'https://testurl.com',
'https://anotherurl.com',
'https://athirdurl.com' // This is the valid one
];
export function validate(key) {
var result;
urls.forEach(function (url) {
result = fetch(`${url}/${key}/validate`)
.then((response) => response.json())
.then((json) => {
if (json.license.valid) {
return json;
} else {
Promise.reject(json);
}
});
});
return result;
}
The above is not working because of the async promises. How can I iterate my urls and return when the first valid == true is hit?
Let me throw a nice compact entry into the mix
It uses Promise.all, however every inner Promise will catch any errors and simply resolve to false in such a case, therefore Promise.all will never reject - any fetch that completes, but does not have license.valid will also resolve to false
The array Promise.all resolves is further processed, filtering out the false values, and returning the first (which from the questions description should be the ONLY) valid JSON response
const urls = [
'https://testurl.com',
'https://anotherurl.com',
'https://athirdurl.com' // This is the valid one
];
export function validate(key) {
return Promise.all(urls.map(url =>
fetch(`${url}/${key}/validate`)
.then(response => response.json())
.then(json => json.license && json.license.valid && json)
.catch(error => false)
))
.then(results => results.filter(result => !!result)[0] || Promise.reject('no matches found'));
}
Note that it's impossible for validate to return the result (see here for why). But it can return a promise for the result.
What you want is similar to Promise.race, but not quite the same (Promise.race would reject if one of the fetch promises rejected prior to another one resolving with valid = true). So just create a promise and resolve it when you get the first resolution with valid being true:
export function validate(key) {
return new Promise((resolve, reject) => {
let completed = 0;
const total = urls.length;
urls.forEach(url => {
fetch(`${url}/${key}/validate`)
.then((response) => {
const json = response.json();
if (json.license.valid) {
resolve(json);
} else {
if (++completed === total) {
// None of them had valid = true
reject();
}
}
})
.catch(() => {
if (++completed === total) {
// None of them had valid = true
reject();
}
});
});
});
}
Note the handling for the failing case.
Note that it's possible to factor out those two completed checks if you like:
export function validate(key) {
return new Promise((resolve, reject) => {
let completed = 0;
const total = urls.length;
urls.forEach(url => {
fetch(`${url}/${key}/validate`)
.then((response) => {
const json = response.json();
if (json.license.valid) {
resolve(json);
}
})
.catch(() => {
// Do nothing, converts to a resolution with `undefined`
})
.then(() => {
// Because of the above, effectively a "finally" (which we
// may get on Promises at some point)
if (++completed === total) {
// None of them had valid = true.
// Note that we come here even if we've already
// resolved the promise -- but that's okay(ish), a
// promise's resolution can't be changed after it's
// settled, so this would be a no-op in that case
reject();
}
});
});
});
}
This can be done using SynJS. Here is a working example:
var SynJS = require('synjs');
var fetchUrl = require('fetch').fetchUrl;
function fetch(context,url) {
console.log('fetching started:', url);
var result = {};
fetchUrl(url, function(error, meta, body){
result.done = true;
result.body = body;
result.finalUrl = meta.finalUrl;
console.log('fetching finished:', url);
SynJS.resume(context);
} );
return result;
}
function myFetches(modules, urls) {
for(var i=0; i<urls.length; i++) {
var res = modules.fetch(_synjsContext, urls[i]);
SynJS.wait(res.done);
if(res.finalUrl.indexOf('github')>=0) {
console.log('found correct one!', urls[i]);
break;
}
}
};
var modules = {
SynJS: SynJS,
fetch: fetch,
};
const urls = [
'http://www.google.com',
'http://www.yahoo.com',
'http://www.github.com', // This is the valid one
'http://www.wikipedia.com'
];
SynJS.run(myFetches,null,modules,urls,function () {
console.log('done');
});
It would produce following output:
fetching started: http://www.google.com
fetching finished: http://www.google.com
fetching started: http://www.yahoo.com
fetching finished: http://www.yahoo.com
fetching started: http://www.github.com
fetching finished: http://www.github.com
found correct one! http://www.github.com
done
If you want to avoid the fact of testing each URL, you can use the following code.
const urls = [
'https://testurl.com',
'https://anotherurl.com',
'https://athirdurl.com' // This is the valid one
];
export function validate(key) {
return new Promise((resolve, reject) => {
function testUrl(url) {
fetch(`${url}/${key}/validate`)
.then((response) => response.json())
.then((json) => {
if (json.license.valid) {
resolve(json);
return;
}
if (urlIndex === urls.length) {
reject("No matches found...");
return;
}
testUrl(urls[urlIndex++]);
});
}
let urlIndex = 0;
if (!urls.length)
return reject("No urls to test...");
testUrl(urls[urlIndex++]);
});
}

"A promise was created in a handler but was not returned from it"

When a user clicks on a button (#lfdsubmit), it calls the function (LFD_SearchContainer()) that should return a promise. But the errors happens at
LFD_SearchContainer('EISU1870725')
.then(container => {
ST2.db2(container);
})
What is wrong?
Code: (don't completely trust the commented out parts to guide you through this code -- i forgot to update some of them)
function LFDTrack () {
function LFD_SearchContainer (requestedContainer) {
return new Promise((resolve, reject) => {
let lfd_scanparams = { TableName: 'lfd_table1' }
db.scan(lfd_scanparams, (err, containers) => {
if (err) {
reject(err);
} else {
containers = containers.Items;
let requestedContainers = []; // different variable than arg
let containerObject; // this will be the resolved object
// this will return the object of the searched container
let findIt = _.forEach(containers, container => {
if (container.container === requestedContainer) {
containerObject = container;
}
});
containerObject = findIt[0];
//console.log(findIt[0]);
resolve(containerObject.container);
}
});
});
}
$(function() {
$("#lfdsubmit").click(function (e) {
e.preventDefault();
let lsd_modaltitle = $("#lfdmodaltitle");
let lsd_modalcontent = $("#lfdmodalcontent");
LFD_SearchContainer('EISU1870725')
.then(container => {
ST2.db2(container); // will send the object
})
.catch(error => {
console.log(error);
});
});
});
}
If ST2.db2(container); returns a promise, you need to change that line to
return ST2.db2(container);
If it doesn't, you can put return null; behind it, like this:
ST2.db2(container);
return null;
Since you didn't provide the definition for ST2, I can't know whether or not the db2 method returns a promise. :)
The error is explained by the author of bluebird here.

Categories