Promise.all and then (js) - javascript

I'm kind of new with the whole promise thing so I might be doing something really wrong. If anyone can enlighten me, all advices/informations are welcome.
So here's the code of what I'm trying to accomplish (simplified and absolutly not optimal for understanding purpose):
// Get data from webservice
$scope.sendGet(id, option).then(function (response){
// Fill the model
$scope.model[option] = response.data;
}).then(function(){
if(option == $scope.PROFILES){
var p1 = new Promise((resolve, reject) => {
$scope.getX1($scope.model[option][0][0].id);
});
var p2 = new Promise((resolve, reject) => {
$scope.getX2($scope.model[option][0][0].id);
});
var p3 = new Promise((resolve, reject) => {
$scope.getX3($scope.model[option][0][0].id);
});
var p4 = new Promise((resolve, reject) => {
$scope.my_data = JSON.parse($scope.model[option][0][0].list);
});
// Execute all promises to get the data
Promise.all([p1,p2,p3,p4]).then(() => {
debugger;
// Do some validation and extra formatting on the data we just downloaded
$scope.update();
});
}
}).then(function(){
// Display the data to the user
$scope.move(option, 1, $scope.EDITING);
});
The intended behavior here is:
Get data -> With this data, use id to get data from 4 sources (the 4 promises) -> Once all the data is downloaded, update some references and do some cleaning -> move (which is a method that updates the view and do some other stuff UI related)
But for some reason, the debugger; and the $scope.update(); never get executed. I tried moving these in the same .then as the $scope.move() function but then it executes before the data from the Promise.all has been retrieved.

You never resolve promises 1-4, so the "success callback" to Promise.all(...).then never fires. In the callback given to the constructor of each promise, call resolve with the data each promise is getting.
// ...
var p1 = new Promise((resolve, reject) => {
resolve($scope.getX1($scope.model[option][0][0].id));
});
// ...
This is how you "return" data, so to speak, from a Promise. Please see this article for details.
EDIT: if $scope.getX1 itself returns a Promise, you can simply assign it to p1, ie:
var p1 = $scope.getX1($scope.model[option][0][0].id);

Related

Using await in javascript after returning a promise

I have a function that needs to be executed. I used the GLTF parser to retrieve the vertices and faces. However it does not execute in the right time. This is the function that I currently have.
public parseGltfBlobs(toAdd: asmTreeNodeScene[]) {
const loader = new GLTFLoader();
for (let i = 0; i < toAdd.length; i++) {
let componentId: number = toAdd[i].componentId;
let isPart: boolean = toAdd[i].type == "part";
// Handle surface mesh requests for parts for server endpoint in json format
if (isPart) {
let surfaceMeshKey: string = componentId.toString() + "." + this.asmStateServ.decimation.toString();
// Grab the byte array
let gltfBytes = <ArrayBuffer>this.assemblyFetchServ.surfaceMeshesGltf.get(surfaceMeshKey)['gltfBytes'];
return new Promise((resolve, reject) => {
loader.parse(gltfBytes, "", gltf => {
let mesh;
let vertices;
let faces;
mesh = <THREE.Mesh>gltf.scene.children[0];
vertices = mesh.geometry.attributes.position.array;
faces = mesh.geometry.index.array;
let surfaceMesh: Object = {
"component_id": componentId,
"vertices": vertices,
"faces": faces,
}
resolve(this.assemblyFetchServ.surfaceMeshes.set(surfaceMeshKey, surfaceMesh));
console.log("Stored data in surfaceMeshes map")
// Emit finished event
}
)
})}
}
}
The function is being called in the following way.
this.requestMissingResources(toAdd, this.isGLTF).subscribe(
() => {
},
(err) => {
console.log(err);
},
async () => {
if(this.isGLTF) {
console.log("Parsing gltf blobs");
await this.parseGltfBlobs(toAdd);
console.log("End of parsing gltf blobs")
}
However, I still do not receive the output promise. I was wondering why that is and what I am doing wrong out here? It would mean a lot if the correct code is given to me as that would help me out a lot as I am very new to this.
Moving answers from comments.
Looks like your promise doesn't have/call the resolve callback to even know it is finished.
When creating a promise define callbacks to mark it as completed successfully or failed. You use resolve and reject callbacks for it as so:
new Promise((resolve, reject) => { ... do something ... resolve(data); })
I don't see anything receiving the result of the promise anyway, nor returning any results from the promise. If you're not planning on returning any info from promise at least call resolve with a boolean in it to mark it as completed for you to know if you can proceed further or not.
The resolve(data) or reject(data) will help you correctly handle the promise. Try/catch works on catching these outputs.
try {
// if promise resolved successfully you will get data in the data variable
const data = await function_returning_a_promise();
}
catch (error) {
// if promise rejected you will catch an error here
console.error(error);
}
use try/catch to handle promises with await as above.
Is there anything asynchronous that you require a promise to parse it? Is this.assemblyFetchServ.surfaceMeshes.set asynchronous? If so you probably should wait for it to complete as well with another await. And if not you probably don't even need a promise at all.
So what I mean is:
const result = await this.assemblyFetchServ.surfaceMeshes.set(surfaceMeshKey, surfaceMesh);
If that's an asynchronous function you should wait for it to complete first. And if it is not you probably shouldn't even use promises in the first place.
I'd recommend reading through https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise carefully. Back in time it helped me a lot to understand promises myself.

JavaScript Promises with AJAX

I am trying to write a series of AJAX requests into a dictionary.
I am attempting to use promises for this, however I am either writing the promise syntax incorrectly, or what I think may be happening is that the function is actually completing (for loop is done, and AJAX requests sent) but the AJAX requests are still not returned. Therefore this is still returning an empty dictionary.
let dict = {};
let activeMachines = ["41", "42", "43"];
let dataPromise = new Promise (function (resolve, reject)
{
for (let i = 0; i < activeMachines.length; i++)
{
let machineID = activeMachines[i]
let getAPIData = new XMLHttpRequest();
let url = 'http://127.0.0.1:8000/processes/apidata/' +machineID + '/';
getAPIData.open('GET', url);
getAPIData.send();
getAPIData.onload = function()
{
let APIData = JSON.parse(getAPIData.responseText);
dict['machine_' + machineID] = APIData[0].author_id;
dict['temp' + machineID] = APIData[0].tempData; //get value
dict['humid' + machineID] = APIData[0].humidData;
timeValue = String((APIData[0].dateTime));
dict['time' + machineID] = new Date(timeValue);
console.log("done");
}
}
resolve();
});
dataPromise.then(function() {console.log(dict);});
Is there a way to "sense" when all of the XMLHTTPRequests have returned?
#Rafael's answer will work, but it doesn't illuminate much about what's going wrong, since you're trying to grok the concept of Promises and write one yourself.
Fundamentally I think your approach has two missteps: 1. creating a single Promise that handles calls to all of your arbitrary list of "activeMachines", and 2. putting your resolve() call in the wrong place.
Usually a Promise looks like this:
const myPromise = new Promise(function(resolve, reject) {
doSomeAsyncWork(function(result) {
// Some kind of async call with a callback function or somesuch...
resolve(result);
});
}).then(data => {
// Do something with the final result
console.log(data);
});
You can simulate some kind of arbitrary asynchronous work with setTimeout():
const myPromise = new Promise(function(resolve, reject) {
// Resolve with "Done!" after 5 seconds
setTimeout(() => {
resolve("Done!");
}, 5000);
}).then(data => {
console.log(data); // "Done!"
});
However your original code puts the resolve() call in a weird place, and doesn't even pass it any data. It looks sorta equivalent to this:
const myPromise = new Promise(function(resolve, reject) {
// Resolve with "Done!" after 5 seconds
setTimeout(() => {
// Doing some work here instead of resolving...
}, 5000);
resolve();
}).then(data => {
console.log(data); // This would be "undefined"
});
Where you're doing a console.log("done"); in your original code is actually where you should be doing a resolve(someData);!
You're also trying to do side effect work inside of your Promise's async function stuff, which is really weird and contrary to how a Promise is supposed to work. The promise is supposed to go off and do its async work, and then resolve with the resulting data -- literally with the .then() chain.
Also, instead of doing multiple asynchronous calls inside of your Promise, you should generalize it so it is reusable and encapsulates only a single network request. That way you can fire off multiple asynchronous Promises, wait for them all to resolve, and then do something.
const activeMachines = ["41", "42", "43"];
// Make a reusable function that returns a single Promise
function fetchAPI(num) {
return new Promise(function(resolve, reject) {
const getAPIData = new XMLHttpRequest();
const url = "http://127.0.0.1:8000/processes/apidata/" + num + "/";
getAPIData.open("GET", url);
getAPIData.send();
getAPIData.onload = function() {
const APIData = JSON.parse(getAPIData.responseText);
const resolveData = {};
resolveData["machine_" + num] = APIData[0].author_id;
resolveData["temp" + num] = APIData[0].tempData; //get value
resolveData["humid" + num] = APIData[0].humidData;
timeValue = String(APIData[0].dateTime);
resolveData["time" + num] = new Date(timeValue);
resolve(resolveData);
};
});
}
// Promise.all() will resolve once all Promises in its array have also resolved
Promise.all(
activeMachines.map(ea => {
return fetchAPI(ea);
})
).then(data => {
// All of your network Promises have completed!
// The value of "data" here will be an array of all your network results
});
The fetch() API is great and you should learn to use that also -- but only once you understand the theory and practice behind how Promises actually operate. :)
Here's an example of the Fetch API which uses Promises by default:
let m_ids = [1,2,3,4];
let forks = m_ids.map(m => fetch(`http://127.0.0.1:8000/processes/apidata/${m}`));
let joined = Promise.all(forks);
joined
.then(files => console.log('all done', files))
.catch(error => console.error(error));
I hope this helps!

The batch function passed to the ".run" method didn't return a promise

Although I do realize what the error means and why it happens, I think I have a use case that goes outside the expected. I am using Word.run() inside another promise, like so:
return new Promise((resolve, reject) => {
window.Word.run(context => {
// do stuff with context
resolve(someData);
});
});
So, if I understood it correctly, this resolves my promise, but leaves the .run method hanging since there's no return context.sync() at the end? Or did I get it wrong? If I'm right, how can I rewrite the example above to keep .run working properly?
If you want your promise to resolve after the context syncs...
return new Promise((resolve, reject) => {
window.Word.run(context => {
// do stuff with context
return context.sync().then(function() {
resolve(someData); // promise will resolve after sync resolves
});
});
});
If you don't need to resolve after but just want to sync the context at some point in the future, you can actually do this and it won't wait for the sync:
return new Promise((resolve, reject) => {
window.Word.run(context => {
// do stuff with context
resolve(someData); // this will resolve the promise
return context.sync(); // this will actually still happen since the function never returns
});
});

Issue with Nodejs promise

I have a sequence of function calls, connected with ES6 promises. Apparently, there is something wrong with this implementation, as API calls to the endpoint are not returning anything and the browser is stuck waiting for a response.
Please advise.
module.exports.insertTreatmentDetails = function (req, res) {
var doctorId = 10000
var departmentId = 10000
var procedureid = 10000
var hospitalSchema = new hospitalModel();
var p = new Promise(function (resolve, reject) {
counterSchema.getNext('Treatment.doctor.doctorId', collection, function (doctorId) {
doctorId = doctorId;
})
counterSchema.getNext('Treatment.departmentId', collection, function (departmentId) {
departmentId = departmentId
})
counterSchema.getNext('Treatment.procedureid', collection, function (procedureid) {
procedureid = procedureid
})
}).then(function () {
setData()
}).then(function (){
hospitalSchema.save(function (error, data) {
if (error) {
logger.error("Error while inserting record : - " + error)
return res.json({ "Message": error.message.split(":")[2].trim() });
}
else {
return res.json({ "Message": "Data got inserted successfully" });
}
});
});
};
The short answer is that you aren't calling resolve or reject inside the first promise in your chain. The promise remains in a pending state. Mozilla has a good basic explanation of promises.
How to Fix
It appears that you want to retrieve doctorId, departmentId, and procedureId before calling setData. You could try to wrap all three calls in one promise, checking whether all three have returned something in each callback, but the ideal is to have one promise per asynchronous task.
If it's feasible to alter counterSchema.getNext, you could have that function return a promise instead of accepting a callback. If not, I would recommend wrapping each call in its own promise. To keep most true to what your code currently looks like, that could look like this:
const doctorPromise = new Promise((resolve, reject) =>
counterSchema.getNext('Treatment.doctor.doctorId', collection, id => {
doctorId = id;
resolve();
}));
Then you could replace the first promise with a call to Promise.all:
var p = Promise.all([doctorPromise, departmentPromise, procedurePromise])
.then(setData)
.then(/* ... */);
Promises allow you to pass a value through to the next step, so if you wanted to get rid of your broadly-scoped variables (or set them in the same step where you call setData), you could just pass resolve as your callback to counterSchema.getNext and collect the values in the next step (also how you'd want to do it if you have counterSchema.getNext return a promise:
Promise.all([/* ... */])
.then(([doctorID, departmentID, procedureID]) => {
// If you aren't changing `setData`
doctorId = doctorID;
departmentId = departmentID;
procedureid = procedureID;
setData();
// If you are changing `setData`
setData(doctorID, departmentID, procedureID);
}).then(/* ... */).catch(/* I would recommend adding error handling */);

Promise not returning properly

I have the following function. It calls itself repeatedly and iterates through ftp servers checking for new files. I'm trying to make it a promise so that I can operate().then(function(newFilesObject), but I can't get the .then on operate to activate. It does attempt to resolve it but doesn't send through. By the way, newFiles is a global variable that gets the files per server appended to it. If more code is wanted I can post or github it.
function operate(){
return new Promise(function(resolve, reject){
if(servers[i]){
if(i!== 0) ftp = new JSFtp(servers[i].server)
local = servers[i].local
remote = servers[i].remote
localFiles = fs.readdirSync(local)
}else{
console.log('trying to resolve')
console.log(newFiles)
resolve(newFiles)
}
gatherFiles(remote).then(function(files){
if(files.length>0){
downloadNew(files).then(function(){
console.log('Done: ' + servers[i].server.host)
i++
operate()
})
}else{
console.log('No updates: ' + servers[i].server.host)
i++
operate()
}
})
})
}
operate().then(function(files){
console.log('files: ' + files)
})
The promises in the code sample do not return as their resolvers or rejectors are not always invoked. In fact, resolve is only invoked when i === 0. According to the Promises/A+ specification, promises may only be transitioned to a fulfilled state by invoking resolve. It also can only be transitioned to a rejected state by invoking reject or throwing an exception from within the executor. Therefore, reaching the end of the executor without invoking either or passing one as a callback ensures the promise remains in pending state indefinitely.
The goal you seek may be achieved with a little refactoring. Considering the following as your goal:
Sequentially through each FTP server...
Read a given directory for a list of files
Compare list of files to one stored locally to determine new ones
If there are new ones, download them sequentially
Return a list of all newly downloaded files
Data
var knownFTPServers = [{
'localDirectory': 'sub/',
'localFilepaths': ['docA.json', 'docB.json'],
'remoteDirectory': 'remsub/',
'remoteFilepaths': [],
'jsftpHandle': undefined,
'host': 'example.com'
},
{
'localDirectory': 'root/',
'localFilepaths': ['file1.txt', 'file2.txt'],
'remoteDirectory': 'remroot/',
'remoteFilepaths': [],
'jsftpHandle': undefined,
'host': 'geocities.com'
}];
Logic
function pullNewFilesFromFTPServer(ftpServer) {
return new Promise(function (resolve, reject) {
var handle = new JSFtp(ftpServer);
ftpServer.jsftpHandle = new JSFtp(ftpServer);
// Returns a promise for reading a directory from JSFtp server
// resolves with file list
// rejects with FTP error
function readdir(directory) {
return new Promise(function (resolve, reject) {
handle.ls(ftpServer.remoteDirectory, function (err, res) {
if (err) return reject(err);
resolve(res);
});
});
}
// Returns a promise for downloading a file from a remote JSFtp server
// resolves with the filepath of the downloaded filepath
// rejects with FTP error
function downloadFile(path) {
return new Promise(function (resolve, reject) {
handle.get(path, path, function (err) {
if (err) return reject(err);
resolve(path);
});
});
}
// get all remote filepaths on server
readdir(ftpServer.remoteDirectory)
// filter out filepaths already present locally
.then(function (remoteFilepaths) {
return remoteFilepaths.filter(function (path) {
return ftpServer.localFilepaths.indexOf(path) < 0;
});
})
// download new filepaths sequentially
// reduce turns the array of new filepaths into a promise chain
// return new filepaths after completing the promise chain
.then(function (newFilepaths) {
return newFilepaths.reduce(function (previousDownloadPromise, newPath) {
return previousDownloadPromise.then(function () {
return downloadFile(newPath);
});
}, Promise.resolve())
.then(function () { return newFilepaths; });
})
// resolve server promise with new filepaths or reject with errors
.then(resolve, reject);
});
}
var allFilesDownloaded = [];
knownFTPServers.reduce(function (previousServerPromise, server) {
return previousServerPromise.then(function (filesDownloaded) {
allFilesDownloaded = allFilesDownloaded.concat(filesDownloaded);
return pullNewFilesFromFTPServer(server);
});
}, Promise.resolve([]))
.then(function () {
console.log(allFilesDownloaded);
}, function (err) {
console.err(err);
});
Though it may seem a little more complicated in some places, the actions of each function are more modular. The idea that is somewhat unintuitive is using Array.prototype.reduce to turn an array of data into an array of promises executed sequentially.
Since creating a promise to download a file attempts to download the file immediately, one can't create all the promises at once if one intends to download them one at a time. Otherwise, the sequence might look a somewhat simpler.

Categories