Script only prints last file in array instead of all files [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 10 months ago.
I'm reading about Promises in JavaScript The Definitive Guide by Flannagan 7ed. In the book there is a script which shows how to build a Promise chain dynamically for an arbitrary number of URLs. The script is as follows:
function fetchSequentially(urls) {
// We'll store the URL bodies here as we fetch them
const bodies = [];
// Here's a Promise-returning function that fetches one body
function fetchOne(url) {
return fetch(url)
.then(response => response.text())
.then(body => {
// We save the body to the array, and we're purposely
// omitting a return value here (returning undefined)
bodies.push(body);
});
}
// Start with a Promise that will fulfill right away (with value undefined)
let p = Promise.resolve(undefined);
// Now loop through the desired URLs, building a Promise chain
// of arbitrary length, fetching one URL at each stage of the chain
for (url of urls) {
p = p.then(() => fetchOne(url));
}
// When the last Promise in that chain is fulfilled, then the
// bodies array is ready. So let's return a Promise for that
// bodies array. Note that we don't include any error handlers:
// we want to allow errors to propagate to the caller.
return p.then(() => bodies);
}
//The script was run as below
//I added the line below to declare the urls array
let urls = ['/data.txt', '/readme.txt', '/textfile.txt'];
//the line below is from the book
fetchSequentially(urls)
.then(bodies => {
console.log(bodies)
})
.catch(e => console.error(e));
I added the let urls line to run the script to fetch 3 text files on my PC.
When the script runs it seems to only fetch the last file textfile.txt, and it prints out the contents of the third file 3 times in the console. I thought the script would retrieve the contents of all 3 files, add them to the bodies array, and then log the contents of all 3 files to console.
Can anyone spot why this isn't working?

It looks like this is the section that's causing problems:
for(url of urls) {
p = p.then(() => fetchOne(url));
}
Here you're creating a global variable url, and since it's running asynchronously, fetchOne(url) is using the last instance of it.
Instead you can do something like:
for(let url of urls) {
p = p.then(() => fetchOne(url));
}
This creates a local instance of url for each iteration.
This sort of style of programming of iterating through arrays asynchronously can introduce subtle errors like this one, so I'd recommend a style that unambiguously creates a new instance per iteration. Something like:
urls.forEach(function (url) {
p = p.then(() => fetchOne(url));
});
Though for this sort of thing with multiple promises, you might just want to do a .map with Promise.all:
return Promise.all(urls.map(fetchOne)); // instead of promise chaining with p

Related

when I use the return keyword in my code, I get only the first value of a list return instead of all

I wrote a function which when I use the return keyword, I only get the first item of the list returned instead of all items. The code works properly without the return keyword.
code snippet
function generateNames(){
for (let i = 0; i < 5; i++) {
const playersName = fetch('https://www.balldontlie.io/api/v1/players?per_page=5')
.then(response => response.json())
.then(json => console.log(json['data'][i]['first_name']))
// return playersName
}
}
generateNames()
The result i get is;
Ike
Ron
Jabari
MarShon
Lorenzo
but when I uncomment the, return playersName line of code, I only get the first value returned.
The result i get is;
Ike
After refactoring the code:
async function generateNames() {
var playerList = [];
const playersName = await fetch('https://www.balldontlie.io/api/v1/players?per_page=5')
.then(response => response.json())
.then(json => {
for (let i = 0; i < 5; i++)
playerList.push(json['data'][i]['first_name'])
})
// return playersName
return playerList;
}
(async () => console.log(await generateNames()))()
I think this is what you want:
async function fetchPlayerNames( count = 1 ) {
let playersResponse = await fetch(`https://www.balldontlie.io/api/v1/players?per_page=${count}`)
let players = await playersResponse.json()
let desiredPlayers = players.data.slice(0, count)
return desiredPlayers.map( player => player.first_name )
}
(async function main() {
let names = await fetchPlayerNames(5)
console.log(names)
})()
Some notes:
async/await is easier to work with than chaining .then handlers, so if your function can be marked async (and some cannot), you should have a strong preference to avoid any .then or .catch inside that function. Your refactor makes the function async, but continues to use .then.
Your refactor won't work anyway, because return playerList will execute before either of the .then functions executes. As a result, I'd expect your refactor to return an empty array immediately every time.
Even though your refactor immediately returns an empty dataset, it still makes the API call five times. That is silly. The URL suggests that the API response can include multiple players, and so your plan should be: (1) make a single request that contains all the rows you want, and then (2) extract the items from that list that you want. If you are careless about making extra network calls, the people who own the API may ultimately block your application because it wastes resources.
Avoid hard-coding things like the 5. That is what is known as a "magic number". Instead, your function should be provided with the desired number. Calling code will get the correct number from a settings file or user input or something else. My version takes it as an argument, but hard-codes it at the call site because there's not really a better place in such a small code sample.
I am going to criticize your naming. Naming matters. Your function does not "generate" names -- it does not create or invent names randomly, but that is what the English word "generate" implies. Your function downloads names from the internet (the correct verb for which is "fetch"). If you name your functions badly, your app will become a mess.

JS - Failure to correctly settle an array of promises before moving on to the next part

I've been encountering an issue regarding JS promise use, and hopefully it is simply that I am missing something very obvious.
Essentially, I attempt to read multiple JSON files at once and push their content to an array belonging to another object, then perform operations on the elements on this array. Therefore, the array needs to be filled before operations are attempted on them. However, despite me using promises to theoretically make sure the order is correct, it seems what I've written fails at doing that.
How do I fix this issue?
Here are snippets of the code I'm using, where the issue arises:
This is the function where I push the extracted objects to my array:
function pushNewRoom (ship, name_json_folder, elem, id) {
let promiseRoom = new Promise ((resolve, reject) => {
let newRoom = gf.getJSONFile(name_json_folder + '/' + elem + ".json")
// note: getJSONFile allows me to grab a JSON object from a file
.then(
(data) => {
data.name = elem;
ship.rooms.push(data);
return data;
}).then((newRoom) => {
resolve(newRoom);
}).catch((reject) => { // if the JSON file doesn't exist a default object is generated
let newRoom = new Room (elem, id);
ship.rooms.push(newRoom);
resolve(newRoom);
});
});
return promiseRoom;
}
And this is the part that calls that function and performs the operations I need after that:
exports.generateRoomsByLayout = function (name_json_folder, ship)
{
ship.rooms = [];
console.log("reached step 1");
// First execution step: get a JSON file
gf.getJSONFile(name_json_folder + "/_base_layout.json")
.then(function (shipmode){
// Note: shipmode is a JSON object that acts as a blueprint for the operations to follow.
// Importantly here, it contains an array, layout, containing the names of every other JSON file I will need to perform the operations.
console.log("reached step 2");
Promise.allSettled(shipmode.layout.map(function (elem, index){pushNewRoom(ship, name_json_folder, elem, index);})
// note: this is where my issue happens
).then(function (){
console.log("reached step 3");
// Operations on the results that were pushed to ship.rooms by pushNewRoom()
}).then(function (shipmode) {
console.log("reached step 4");
// More operations on the results
}).catch((err) => {
});
return Promise.resolve(shipmode);
}).catch(function (errRejection) {
// Error handling
console.log(errRejection);
});
};
The issue happens right at the Promise.allSettled() line. Rather than waiting for the promises supposedly generated with ship.layout.map(), that would then become an iterable array, the program continues on.
I suppose this is because Promise.allSettled() does not wait for the array to be generated by map() before moving on, but have been unable to fix the issue, and still doubt that this is the explaination. Could anyone enlighten me on what I am doing wrong here?
If what I'm asking is unclear then please do tell me, and I'll try my best to clarify.
edit: I suspect it is linked to Promise.allSettled() not waiting until map() fills the array to consider every promise inside the array settled, as its length seems to be 0 right at step 3, but I am not sure.
Nevermind, I'm an idiot.
The map() method's callback (?) won't (obviously) return an object (and therefore, a promise) if you do not tell it to. Therefore,
shipmode.layout.map(function (elem, index){pushNewRoom(ship, name_json_folder, elem, index);})
needs to be
shipmode.layout.map(function (elem, index){return pushNewRoom(ship, name_json_folder, elem, index);})

Assure synchronous execution of nested Arrays within arrays (Promise.all - in Promise.all)

I have a problem in assuring the synchornous execution of an array which is executed within another array. The first array is NOT directly link to the "Nested" one - it just assures the the second ("nested") array is executed as many times, as the first one has recrods resp. documents.
To illustrate the Problem - here is the code I am talking about
Promise.all(
room.connections.map(connection => {
Question.find({room: room.title}).then(questions => {
return Promise.all(
questions.map(question => {
if (question.answers.length !== 2) {
question.answers.push({ email: connection.userId, own: "", guess: "" });
console.log('SAVE ANSWER');
return question.save();
}
}),
)
});
})
).then(() => {
console.log('SENDING GAME READY TO BOTH!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
io.of("/game")
.in(room.title)
.emit("GameReady", true);
ack(false);
})
So as you see - I am inserting answers into a question array. Besides that there should be inserted as many answers, as I have active connections in another Collection.
I have tried the code above, but obviously the first promise.all resolves, as soon as the two connections where iterated through - without assuring the all answers have already been inserted/saved.
I have initially tried to make the whole thing without the first Promise.all - but had the problem that the "socket.emit" part would then be executed twice (because I have 2 connections in my array usually).
The outermost lambda (connection => { ... }) has a statement body (because the body is surrounded by curly braces), but doesn’t contain a return statement, so the expression room.connections.map(...) is evaluating to a collection full of undefined values. And something like Promise.all([ undefined ]) will immediately resolve.
Try returning Question.find(...) from the outer lambda. That way, the outermost call to Promise.all will receive as argument a collection that’s properly populated with promises.

Loop over javascript object whilst running async calls within

I'm trying to load multiple files using the THREE.XHRLoader and when it completes I want to take the key of that object and add it along with the file to another object (loadedFiles). The problem I have is that whenever I try to retrieve the key for the object that was loaded it always returns the last key in the object array because the callback for the load function gets called after the loop has ended.
I've got something like this:
var loader = new THREE.XHRLoader();
var loaded = 0;
for (var k in filesToLoad) {
loader.load(filesToLoad[k], function(file) {
console.log(k); // This will always return the last key when I want
//it to the return the key that was loaded instead!
loadedFiles[k] = file;
loaded++;
if (loaded == Object.keys(filesToLoad).length) {
console.log(loadedFiles);
}
});
}
You could wrap your THREE calls in a promise then use Promise.all to resolve once all they have. (pseudo code)
let promises = Object.keys(filesToLoad)
.map(k => {
return new Promise((res, rej) => {
loader.load(filesToLoad[k], res, rej); //Assuming loader has success, error callbacks
});
});
Promise.all(promises)
.then(res => console.log(res)); // [file, file, file...]
Obviously depending on your browser support depends on ES6 but there are ES5 compatible promise libraries you can use instead.
Note all your files will be loaded in parallel when each promise is created. The requests will already be pending before they go into Promise.all
Promise.all

How to extract data out of a Promise

I have a promise that returns data and I want to save that in variables. Is this impossible in JavaScript because of the async nature and do I need to use onResolve as a callback?
Can I somehow use this (e.g. wrap it with async/await):
const { foo, bar } = Promise.then(result => result.data, errorHandler);
// rest of script
instead of this?
Promise.then(result => {
const { foo, bar } = result.data;
// rest of script
}, errorHandler);
Note: Bluebird library is used instead of native implementation, and I can't change from Promise to asnyc/await or Generators.
NO you can't get the data synchronously out of a promise like you suggest in your example. The data must be used within a callback function. Alternatively in functional programming style the promise data could be map()ed over.
If your are OK using async/await (you should it's awesome) then you can write code that looks synchronous yet retain the asynchronicity of a promise (see #loganfsmyth comments).
const { foo, bar } = await iAmAPromise.then(result => result.data);
Overall since you are already using ES6 I assume you are also using a transpiler. In which case you should definitely give async/await a try.
Just be sure to weight in the decision that as today they are not yet a ratified specification.
While you can get a value from an awaited Promise inside an async function (simply because it pauses the function to await a result), you can't ever get a value directly "out" of a Promise and back into the same scope as the code that created the Promise itself.
That's because "out of" would mean trying to take something that exists in the future (the eventually resolved value) and putting it into a context (synchronous variable assignment) that already happened in the past.
That is, time-travel. And even if time-travel were possible, it probably wouldn't be a good coding practice because time travel can be very confusing.:)
In general, if you find yourself feeling like you need to do this, it's good sign that you need to refactor something. Note that what you're doing with "result => result.data" here:
Promise.then(result => result.data, errorHandler);
// rest of script
..is already a case of you working with (literally, mapping over) the value by passing it to a function. But, assuming that "// rest of script" does something important related to this value, you probably want to continue mapping over the now updated value with yet another function that then does something side-effect-y with the value (like display the data on the screen).
Promise
.then(({ data }) => data)
.then(data => doSomethingWithData(data))// rest of script
.catch(errorHandler);
"doSomethingWithData" will be called (if it's ever called) at some unknown point in the future. Which is why it's a good practice to clearly encapsulate all that behavior into a specific function and then hook that function up to the Promise chain.
It's honestly better this way, because it requires you to clearly declare a particular sequence of events that will happen, explicitly separated out from the first run through all of your application code's execution.
To put it another way, imagine this scenario, hypothetically executed in the global, top-level scope:
const { foo, bar } = Promise.then(result => result.data, errorHandler);
console.log(foo);
//...more program
What would you expect to happen there? There are two possibilities, and both of them are bad.
Your entire program would have to halt and wait for the Promise to execute
before it could know what "foo" & "bar" would... nay, might be. (this is
what "await," inside an async function, does in fact do: it pauses
the entire function execution until the value is available or an the error is thrown)
foo and bar would just be undefined (this is what actually
happens), since, as executed synchronously, they'd just be
non-existent properties of the top-level Promise object (which is not itself a "value,"
but rather a quasi-Monadic wrapper around getting an eventual value OR an error) which most
likely doesn't even contain a value yet.
let out; mypromise.then(x => out = x); console.log(out)
Only use this code when
you are debugging by hand,
and you know the promise has already succeeded
Behaviour of this code:
While the promise has not resolved yet, out will be undefined.
Once the promise resolves to a failure, an error is thrown.
When the promise resolves to success, (which may be after the console.log), the value of out will change from undefined to the Promise result — maybe in the middle of what you were doing.
In production code, or really any code that runs without you, the code that uses the Promise result should be inside the .then() callback, and not use some out variable. That way:
your code won't be run too early (when the result is still undefined),
and won't run too late (because you don't need 'I think sleeping for 10 seconds should do it' workarounds),
and won't erroneously run when the promise fails.
I have a solution of getting this value "out" if you will. This is a method at backend for uploading multiple files to AWS S3 which must be dealt asynchronously. I also need the responses from S3, so I need the values out of the Promise:
async function uploadMultipleFiles(files) {
const promises = []; //Creating an array to store promises
for (i = 0; i < files.length; i++) {
const fileStream = fs.createReadStream(files[i].path)
const uploadParams = {
Bucket: bucketName,
Body: fileStream,
Key: files[i].filename
}
promises.push(s3.upload(uploadParams).promise()) //pushing each promise instead
//of awaiting, to enable for concurrent uploads.
}
await Promise.all(promises).then(values => {
console.log("values: ", values) //just checking values
result = values; //storing in a different variable
});
return result; //returning that variable
}
The key lines in context with the issue being discussed here are these :
await Promise.all(promises).then(values => {
console.log("values: ", values) //just checking values
res = values; //storing in a different variable
});
return res; //returning that variable
But of course we have to also await in the function that will be calling this :
const result = await uploadMultipleFiles(files);
All you need to do is to extract all you have in your promise by using a .then
yourFunction().then( resp => {
... do what you require here
let var1 = resp.var1;
let var2 = resp.var2;
...
.....
})
yourFunction() should return a Promise
How to Get A Value From A Promise
YES! You can extract value out of a promise!
Do NOT let anyone here say you cannot. Just realize any variable that stores your returned promise value will likely have a short delay. So if you have a JavaScript script page that needs that data outside of the Promise or async-await functions, you may have to create loops, interval timers, or event listeners to wait to grab the value after some time. Because most async-await-promises are REST calls and very fast, that wait would require just a quick while loop!
It is easy! Just set a variable (or create a function) that can access the value inside your async or promise code and store the value in an outside variable, object, array, etc you can check on. Here is a primitive example:
// I just created a simple global variable to store my promise message.
var myDelayedData = '';
// This function is only used to go get data.
// Note I set the delay for 5 seconds below so you can test the delay
const getData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('my promise data'), 5000);
});
}
// I like to create a second async function to get the data
// from the promise object and save the data to my global variable.
const processData = async () => {
let data = await getData();
// Save the delayed data to my global variable
myDelayedData = data;
}
// Start the data call from the promise.
processData();
// Open up your browser, hit F12 to pull up the browser devtools
// Click the "console" tab and watch the script print out
// the value of the variable with empty message until after
// 5 seconds the variable is assigned to the resolved promise
// and apears in the message!
// THAT IS IT! Your variable is assigned the promise value
// after the delay I set above!
// TEST: But let's test it and see...
var end = setInterval(function(){
console.log("My Result: " + myDelayedData);
if(myDelayedData !== ''){
clearInterval(end);
}
}, 1000);
// You should see this in devtools console.
// Each line below represents a 1 second delay.
My Result:
My Result:
My Result:
My Result: my promise data
Most people seeing this code will say "Then why use a Promise, just make a call for the data, pause, and update your application?" True: The whole point of a Promise is to encapsulate data processes inside the promise and take actions while the rest of the script continues.
But... you may need to output a result outside the Promise. You may have other global processes that need that data because it changes the state of the global application, for example. But at least you know you can get to that Promise data if you needed it.

Categories

Resources