I'm trying to convert callbacks to async/await, but found that async/await is much slower than the existing callbacks. Can anyone see what's wrong with my async/await?
for (var i = 0; i < balance; i++) {
tokenOfOwnerByIndex().call(i).then((id) => {
tokenURI().call(id).then((uri) => {
console.log(uri);
});
});
}
for (var i = 0; i < balance; i++) {
var id = await this.getTokenOfOwnerByIndex(i);
var uri = await this.getTokenURI(id);
console.log(uri);
}
In the first version tokenOfOwnerByIndex is called returning a promise. You attach a callback via then, and the loop continues. The promise will eventually resolve, but your for loop is done long before that.
When you use await, you are blocking following code, until the promise resolves. This means that each call to tokenOfOwnerByIndex has to resolve, before the for loop continues.
See my code for an example.
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
console.time('promise');
let c = 10;
for (let i = 0; i < 10; i++) {
sleep(100)
.then(() => {
c--;
if (c === 0) {
console.timeEnd('promise');
}
});
}
console.time('await');
(async () => {
let c = 10;
for (let i = 0; i < 10; i++) {
await sleep(100);
c--;
if (c === 0) {
console.timeEnd('await');
}
}
})();
Related
so let's say I have something like this:
var x = 2;
for(var i = 0; i < 10; i++) {
new Promise(async (resolve, reject) => {
if(someFunctionThatChecksX(x)) {
continue;
}
}
}
I can't use continue because "jump target cannot cross function boundaries" and need a way around that to continue the for loop when that function that checks X results in true. Thanks so much for any assistance.
If you want the for loop to wait for the promise to resolve, put it inside of an async block. You could then use await to get the result of the promise, and continue based off that in the body of the for loop.
Altogether, it looks like this:
async function myFunction() {
var x = 2;
for(var i = 0; i < 10; i++) {
let result = await new Promise(async (resolve, reject) => {
resolve(someFunctionThatChecksX(x))
}
if(result) {
continue;
}
}
}
I want to conditionally break out of a loop like this..
for(let i = 0; i < 3; i++) {
exampleFunction().then(result => {
res = 0 ? i = 3 : null
})
}
I want exampleFunction to run at least 3 times unless it gets the desired result, in which case I want it to stop running.
exampleFunction runs asynchronously. The only way to get it working is using async/await.
const iterateWithExampleFunction = async () => {
for (let i = 0; i < 3; i++) {
console.log('before', i)
await exampleFunction().then(result => {
i = result === 0 ? 3: i;
});
console.log('after', i)
}
};
const exampleFunction = async () => {
return 0;
}
iterateWithExampleFunction();
You can have a count on the outer scope and then do the async call.
let count = 0;
function executeCall() {
exampleFunction().then(result => {
// do something with the result
if (result !== 0 && count !== 3) {
count += 1;
executeCall();
}
});
}
Just await the result, than break:
(async function() {
for(let i = 0; i < 3; i++) {
const result = await exampleFunction();
if(result === 0) break;
}
})();
Hopefully this gives you some ideas
async function poll(f, threshold = 3) {
if (!threshold) {
throw new Error("Value not found within configured amount of retries");
}
const result = await f();
if (result >= 0.5) {
return result;
} else {
return await poll(f, threshold - 1);
}
}
async function task() {
return Math.random();
}
poll(task).then(console.log).catch(console.error);
I am wondering if the two code blocks are basically equivalent:
const executePromises = function(listOfProviders){
let p = Promise.resolve(null);
for(let i = 0; i < listOfProviders.length; i++){
p = p.then(v => listOfProviders[i]());
}
return p;
};
I believe the above is basically equivalent to:
const executePromises = async function(listOfProviders) {
for(let i = 0; i < listOfProviders.length; i++){
await listOfProviders[i]();
}
};
does anyone see a discrepancy?
No. They are not the same. Even if you return the promise as someone pointed out in comments.
Difference:
When using async await with for-loop, it should be remembered that for loop goes to next iteration only when the awaited promise has been resolved in the current iteration.
On the other hand, when .then is used in a for-loop, that for-loop doesn't wait for the promise to resolve before going to the next iteration.
I have tweaked your code to demonstrate this. Observe that Line A is being called at different point of time in each code snippet.
Async await inside for-loop code:
function createPromiseWhichResolveAfterOneSec(i) {
return new Promise((resolve, reject)=>{
setTimeout(()=>{
console.log("About to resolve promise with value:",i);//Line A
resolve(i);
},1000)
})
}
const executePromises = async function(listOfProviders) {
let p;
let i;
for(i = 0; i < listOfProviders.length; i++){
console.log("before", i);
p = await createPromiseWhichResolveAfterOneSec(i);
console.log("after", i);
}
console.log(i);
return p;
};
let listOfProviders = [1,2,3,4,5];
let promise = executePromises(listOfProviders);
promise.then(value => console.log("this is the final value of promise chain:",value)).catch(()=>{});
With then in for-loop:
function createPromiseWhichResolveAfterOneSec(i) {
return new Promise((resolve, reject)=>{
setTimeout(()=>{
console.log("About to resolve promise with value:",i);//Line A
resolve(i);
},1000)
})
}
const executePromises = function(listOfProviders){
let p = Promise.resolve(null);
for(let i = 0; i < listOfProviders.length; i++){
console.log("before", i);
p = p.then(v => createPromiseWhichResolveAfterOneSec(i));
console.log("after", i);
}
return p;
};
let listOfProviders = [1,2,3,4,5];
let promise = executePromises(listOfProviders);
promise.then(value => console.log("this is the final value of promise chain:",value)).catch(()=>{});
I am modernizing some code. It has a piece to load database implemented as:
var customerQueue = async.queue(insertCustomer, DATABASE_PARALLELISM);
customerQueue.drain = function() {
logger.info('all customers loaded');
airportCodeMappingQueue.push(airportCodeMappings);
}
The function insertCustomer used to written with callbacks. I changed it to async/await, as a part of code modernization.
Now, think that I wrote an equivalent of async.queue as:
let customerQueueElements = [];
var customerQueue = {};
customerQueue.push = (customers) => {
customers.forEach(customer => {
customerQueueElements.push(insertCustomer(customer))
});
}
const processQueue = async (queue, parallelism) => {
for (let i = 0; i < queue.length; i += parallelism) {
for (let j = 0; j < parallelism; j++) {
let q = []
if (queue[i + j]) {
q.push(queue[i + j])
}
await Promise.all(q)
}
}
}
I am able now to do await ProcessQueue(customerQueue, DATABASE_PARALLELISM), but the syntax is bad, and I am keeping a visible named variable for each queue.
What would be a good way to handling this?
Also, drain() should be hooked-up to then, right ?
#Bergi is correct, as far as direction. I put together a work-in-process version:
module.exports = function () {
module.internalQueue = []
module.func = undefined
module.parallelism = 1
const process = async () => {
for (let i = 0; i < module.internalQueue.length; i += module.parallelism) {
for (let j = 0; j < module.parallelism; j++) {
let q = []
if (module.internalQueue[i + j]) {
q.push(module.func(module.internalQueue[i + j]))
}
await Promise.all(q)
}
}
module.internalQueue = []
module.drain()
}
module.queue = (func, parallelism = 1) => {
module.func = func
module.parallelism = parallelism
return module
}
module.push = async (customers) => {
module.internalQueue.push(customers)
await process()
}
module.drain = () => {}
return module
}
It is not exactly correct, yet. The signatures are similar to the async package, but my version shares the queue among all instances.
Need to figure out an easy way of creating an instance for each function with a separate e.g. "local" queue. Then, it will basically work like the original one.
Will update as I have progress.
I'm having a problem with my program at the moment in which I am running a promise in the .then of a promise, however it is running the 2nd promise before the first. This is not what I want since it relies on the 1st promise to work. Here is my code (The console.logs are random, but I just came up with stuff on the spot for checking when things run)
new Promise((resolve, reject) => {
for (i = 0; i < 10; i++) {
var query = {
class: result[`class${i}`]
};
dbo.collection("homeworks").find(query).toArray(function(err, result) {
if (err) throw err;
for (i = 0; i < result.length; i++) {
current = current + 1;
console.log(current)
allhomework[current] = result[i];
console.log(allhomework[current])
}
db.close();
});
}
console.log("HOLA");
resolve(allhomework);
})
.then((allhomework) => {
console.log("HOLA MIS AMIGOS")
new Promise((resolve, reject) => {
console.log("v");
for (i = 0; i < 10; i++) {
console.log("uaefshiudvhofdh")
console.log(allhomework[1]);
}
resolve();
}).then(() => {
response.render('index', {
username: req.session.user,
homeworks: allhomework
});
})
})
.catch(console.log)
Well, the best way to solve this would be to use the promise interface for your database and use Promise.all() to track when all the async database operations in your loop are done. But, if you want to code it manually with your existing loop, you just have to keep a counter for when they are all done.
new Promise((resolve, reject) => {
let cntr = 10;
for (let i = 0; i < 10; i++) {
var query = {
class: result[`class${i}`]
};
dbo.collection("homeworks").find(query).toArray(function(err, result) {
if (err) {
db.close();
return reject(err);
}
for (let i = 0; i < result.length; i++) {
current = current + 1;
console.log(current)
allhomework[current] = result[i];
console.log(allhomework[current])
}
--cntr;
if (cntr === 0) {
db.close();
resolve(allHomework);
}
});
}
console.log("HOLA");
});
Other notes about this code:
I'm also wondering why there's a db.close() in the middle of your outer loop. I would think that would cause a problem.
I also added a reject(err) to your if (err) check.
You have loop conflict with your i variable. Put a let for each one to make them separate or change one of them to a different letter.
Here's a version that uses the promise interface in your database. I was able to completely get rid of the inner loop and just let promises collect all the results in order for us. Monbodb's .toArray() returns a promise that resolves with the query result if you don't pass it a callback. We can use that with Promise.all() to collect all the results in order and to tell us when they are all done:
let promises = [];
for (let i = 0; i < 10; i++) {
let query = {
class: result[`class${i}`]
};
promises.push(dbo.collection("homeworks").find(query).toArray());
}
Promise.all(promises).then(allResults => {
// flatten the results into all one array - promises will have kept them in proper order
db.close();
let results = [].concat(...allResults);
// now do whatever you want with the final array of results
}).catch(err => {
db.close();
// handle error here
});