I am trying to loop through records in a database, in order to compile an array (cardsToInsert) that I will write to another database.
I was getting stuck because the array was writing to the database before the loop finished, I know I need to use promises / async functions to achieve what I want, but I'm pretty sure I'm doing something wrong with my promises.
The code works for a few loops (it goes for about 6-10 loops, it's supposed to loop 16 times), but then hangs while trying during wixData.get (or it hangs on a different promise that is part of buildCard).
// wixData.get is a function that returns a promise
async function loopCards(cardsToGet) {
let writeCard
let buildCard
for (let index = 0; index < cardsToGet.length; index++) {
const cardToGet = cardsToGet[index].card
buildCard = await wixData.get("Card", cardToGet)
.then((card) => {
return card
})
.catch((err) => {
let errorMsg = err;
return errorMsg
});
writeCard = await buildingCard(buildCard)
cardsToInsert.push(writeCard)
}
return cardsToInsert
}
What am I doing wrong? (or what is the key thing I'm doing wrong that is stopping this working, I'm sure there is plenty to be improved here!)
UPDATE
I've now updated the code and it loops through fine.
async function loopCards(cardsToGet) {
console.log('Start')
let writeCard
let buildCard
for (let index = 0; index < cardsToGet.length; index++) {
const cardToGet = cardsToGet[index].card
buildCard = wixData.get("Card", cardToGet)
.then(async (card) => {
writeCard = await buildingCard(card)
cardsToInsert.push(writeCard)
})
.catch((err) => {
let errorMsg = err;
return errorMsg
});
}
return cardsToInsert
}
How do I get it to wait for the loop to finish before finally returning cardsToInsert?
Your mix of async/await and .then is not really best practice
This should work, and will return once cardsToInsert is populated
async function loopCards(cardsToGet) {
const cardsToInsert = [];
for (let cardToGet of cardsToGet) {
try {
const card = await wixData.get("Card", cardToGet);
const writeCard = await buildingCard(card);
cardsToInsert.push(writeCard);
}
catch(err) {
let errorMsg = err;
return errorMsg;
}
}
return cardsToInsert;
}
better still, you really don't need to handle any errors here, since the calling function could do that
So it becomes even simpler
async function loopCards(cardsToGet) {
const cardsToInsert = [];
for (let cardToGet of cardsToGet) {
const card = await wixData.get("Card", cardToGet);
const writeCard = await buildingCard(card);
cardsToInsert.push(writeCard);
}
return cardsToInsert;
}
then using it could be like
loopCards(cards)
.then(result => doSomethingWihtResult)
.catch(error => handleError);
or if calling from an async function
try {
let result = await loopCards(cards);
// do something with result
} catch(error) {
// handle Error
}
Related
I Have 2 functions one which uses async and await to grab data and place it into an array.
The second is acts like a checker to see if the user inputs a similar value as seen on the database
function repeatsChecker() {
let api_data_values = []
fetchData().then(data => {
for (let i = 0; i < data.length; i++) {
api_data_values.push(data[i].name)
}
})
return api_data_values
}
// testing for similarities
async function test() {
let name = "Joe"
let test = await repeatsChecker();
console.log(test[0])
}
test()
When I compile a simple if statement everything returns true and when I do console.log(test[0])
it returns undefined?
repeatChecker isn't returning a promise, so the fact that you're awaiting doesn't have any meaningful effect. console.log(test[0]) executes before api_data_values.push(data[i].name).
Try this:
function repeatsChecker() {
return fetchData().then(data => {
return data.map(value => value.name);
});
}
Or with async/await:
async function repeatsChecker() {
const data = await fetchData();
return data.map(value => value.name);
}
The code snippet below is not running in the proper order... the for loop is calling the downloadFile function call over every iteration before any other functions can resolve, which is affecting the number of links returned to the final callback.
What I need is an loop iteration to finish only once the appendLinksList function has resolved and returned a link. I'm assuming I need async to block the functions? How would I use async to get the proper flow instead of the downloadFiles function being called before anything else can resolve a value?
Note that the callback functions have been defined above this code snippet
const entries [ /* Array of objects containing a path_display property */];
let links = [];
for(let i = 0; i < entries.length; i++) {
const appendLinksList = link => {
if(i !== entries.length - 1) return links = [...links, link];
return callback(null, links);
};
const downloadFile = path => {
Dropbox.filesGetTemporaryLink({ path })
.then(file => {
return appendLinksList(file.link);
})
.catch(err => {
return res.status(500).json(err.message);
});
};
downloadFile(entries[i].path_display);
};
You can wrap your loop in an immediately invoked async function.
const linksResolved = await (async() => {
let links = [];
for(let i = 0; i < entries.length; i++) {
const appendLinksList = link => {
if(i !== entries.length - 1) return links = [...links, link];
return callback(null, links);
};
const path = entries[i].path_display;
await Dropbox.filesGetTemporaryLink({ path })
.then(file => {
return appendLinksList(file.link);
})
.catch(err => {
return res.status(500).json(err.message);
});
};
};
return links;
})()
However, the result you want (links) will forever be a promise.
Can only be resolved in an async function. Or you can call a callback with links from inside the immediatelyInvokedAsyncFunction
I have an endpoint which loops through an array and updates the database as follows.
app.post('/create', Authenticate, async (req, res) => {
const {
products,
} = req.body;
const trxProvider = knex.transactionProvider();
const trx = await trxProvider();
try {
const formattedProduct = await Promise.all(products.map(async (product) => {
// Get Current value
const warehouseProducts = await trx('warehouse_products')
.select('available_qty as qty')
.where('product_code', product.product.code)
.first();
const finalQty = warehouseProducts.qty - product.orderQty;
// Update database
await trx('warehouse_products')
.update({ available_qty: finalQty })
.where('product_code', product.product.code);
}));
await trx('ordered_products')
.insert(formattedProduct);
trx.commit();
console.log('Transaction successful');
return send(res, 201, { success: true });
} catch (err) {
console.log(err);
trx.rollback();
const errors = {};
errors.message = 'something went wrong';
return send(res, 500, errors);
}
});
The issue arises when i try to update the same row of the warehouse_products table within the loop.
In the loop initially the qty value is taken from the warehouse_products table for a particular product then an arithmetic operation is done and the qty value is updated.
Ideally if both iterations access the same row, the second iteration's initial qty value should be what the first iteration updated. However the issue is that the second iteration too starts with the initial value of the first iteration. Its almost as if both iterations are happening parallel to each other instead of occurring sequentially.
Since you are using Promise.all it is supposed to happen in paralle. For sequential processing change this code
await Promise.all(products.map(async (product) => {
// logic here
});
to
for(const product of products) {
// logic here
}
Have a look at the definition for Promise.all()
It is typically used after having started multiple asynchronous tasks to run concurrently and having created promises for their results, so that one can wait for all the tasks being finished.
if you don't want to use an external library like Bluebird or Async
you can go with simple for a loop as following
let delay = (t) => new Promise((resolve) => {
setTimeout(() => {
return resolve(new Date().getMilliseconds())
}, t*1000);
});
let array = [1,1,1,1];
//using simple for loop
async function test() {
let out = [];
for (let i = 0; i < 4; i++) {
const res = await delay(array[i]);
out.push(res);
}
return out;
}
test().then(res => {
console.log(res)
})
I'm trying to return an array of objects to a function, and looking at the debugger, it looks like it just returns an empty array.
Here's the function that I'm hoping to use:
// load from json file
loadData(filepath) {
let rooms = [];
fetch(filepath)
.then(
res => res.json()
)
.then(function (data) {
// console.log(data);
for (let i = 0; i < data.rooms.length; i++) {
rooms.push(data.rooms[i]);
}
});
return rooms;
}
I go into the debugger, and I see the rooms from data.rooms getting pushed to my local rooms object, but when I get back up to my init() function that's supposed to use this rooms data:
let rooms = this.loadData(this.datapath); // TODO: make games load from json data
for (let i = 0; i < rooms.length; i++) {
this.addRoom(rooms[i].name, rooms[i].getText, rooms[i].prompts, rooms[i].requirements);
}
It looks like it's passed an empty array. Is there a scoping issue or something I'm missing? I'm not very experienced with fetch/promises/JSON.
Happy to provide any further info that may be useful. Thanks!
The problem is that your local variable rooms will be returned before the async fetch finish.
You can just simple await the fetch statements and make the function async:
async loadData(filepath) {
let rooms = [];
const response = await fetch(filepath);
const data = await response.json();
console.log(data);
for (let i = 0; i < data.rooms.length; i++) {
rooms.push(data.rooms[i]);
}
return rooms;
}
let rooms = await this.loadData(this.datapath);
Try this, if it did not work let me know.
// load from json file
async function call() {
const loadData = async filepath => {
let rooms = [];
return await fetch(filepath)
.then(res => res.json())
.then(async data => {
rooms = await [...data];
return rooms;
});
};
let x = await loadData("https://jsonplaceholder.typicode.com/posts");
console.log("TEST: ", x);
}
call();
I'm trying to get the list of lists from my db, format that list into a string containing 'name' in the list items and return the result. I thought I had the async and await set up properly, but the result is returned early as a promise before the promise is resolved. Any tips on getting an async function to not return until the promise is resolved?
exports.viewListsNamesSMS = async () => {
let formatResult = (messages) => {
let temp = [];
for(let i = 0; i < messages.length; i ++)
{
temp.push(messages[i].name.toString());
}
return "Lists: \n" + temp.join('\n');
}
let getListNames = async () => {
let result = await db.List.find({}, (err, messages) => {
if(err) return err;
return formatResult(messages);
});
return result;
}
let result = getListNames();
return result;
}
db.List.find does not look like something that returns a Promise, so you'll need to Promisify it
exports.viewListsNamesSMS = () => {
let formatResult = (messages) => {
let temp = [];
for(let i = 0; i < messages.length; i ++) {
temp.push(messages[i].name.toString());
}
return "Lists: \n" + temp.join('\n');
}
let getListNames = () => {
return new Promise((resolve, reject) => {
db.List.find({}, (err, messages) => {
if(err) {
reject(err);
} else {
resolve(formatResult(messages));
}
});
}
return getListNames();
}
It may be useful to know that async/await doesn't just wait for any asynchronous operation to complete - it is syntactic sugar for Promises, so await will "wait" for a Promise to resolve
since the code you need is simple, with a single asynchronous operation thta doesn't itself return a Promise, there is no need to even use async/await at all - returning a Promise is all you need, as long as the code that calls this function is written to use Promises (or async await for that matter)
e.g.
viewListNamesSMS().then(results => process(results))
or
results = await viewListNamesSMS();
process(results)