React app can run node.js function which preparing data and sending information to the database in batches.
It takes a lot of time and I would like to add the ability to stop this function right from react app.
const getShopifyOrders = require('./shopify');
const getTrack = require('./tracking');
const Order = require('./model');
async function addOrdersToDB(limit) {
try {
// Get latest order from DB
let latestOrd = await Order.findOne().sort('-order_number');
do {
// Get Shopify Orders
let orders = await getShopifyOrders(
latestOrd ? latestOrd.order_id : 0,
limit
);
latestOrd = orders[0] ? orders[orders.length - 1] : undefined;
// Update array with tracking status
let fullArray = await getTrack(orders);
// Add to DB
let ins = await Order.insertMany(fullArray, { ordered: false });
console.log(`Added ${ins.length} entries`);
} while (latestOrd);
} catch (err) {
console.log(err);
}
}
module.exports = addOrdersToDB;
I tried a lot of things to include in this function including:
while loop: added the variable outside the function - if 'true' - run code, if not - return - it just doesn't work (variable was changed from react using socket.IO)
setTimeout (also setInterval), triger clearTimeout function from react: this doesn't work as setTimeout and setInterval doesn't work in async function
after that:
made (actually fond here on stackoverflow) new function to promisify setTimeout to be able to use in async function:
const setTimeout2 = (callback, ms) => {
return new Promise(
resolve =>
(to = setTimeout(() => {
callback();
resolve();
}, ms))
);
};
async function addOrdersToDB(limit) {
do {
await setTimeout2(async () => {
try {
// some code here
} catch (err) {
console.log(err);
}
}, 400);
} while (latestOrderExist);
}
function clearTO() {
setTimeout(() => {
console.log('clearTO');
clearTimeout(to);
}, 3000);
}
This for some reason doesn't iterate.
Is there solution for this?
Thanks!
To abort the do/while loop, you will need to add an additional test to that loop that is some variable that can be modified from the outside world. Also, note that the additional test only works here because you're using await inside the loop. If there was no await inside the loop, then the loop would be entirely synchronous and there would be no ability to change a variable from outside the loop while the loop was running (because of nodejs' single-threadedness).
Since this is a server (and globals are generally bad), I will assume we should not use a global. So instead, I would restructure addOrdersToDB() to return a data structure that contains both the promise the existing version returns and an abort() function the caller can call to stop the current processing. This also permits multiple separate calls to addOrdersToDB() to be running, each with their own separate abort() method.
function addOrdersToDB(limit) {
let stop = false;
function abort() {
stop = true;
}
async function run() {
try {
// Get latest order from DB
let latestOrd = await Order.findOne().sort('-order_number');
do {
// Get Shopify Orders
let orders = await getShopifyOrders(
latestOrd ? latestOrd.order_id : 0,
limit
);
latestOrd = orders[0] ? orders[orders.length - 1] : undefined;
// Update array with tracking status
let fullArray = await getTrack(orders);
// Add to DB
let ins = await Order.insertMany(fullArray, { ordered: false });
console.log(`Added ${ins.length} entries`);
} while (!stop && latestOrd);
// make resolved value be a boolean that indicates
// whether processing was stopped with more work still pending
return !!(latestOrd && stop);
} catch (err) {
// log error and rethrow so caller gets error propagation
console.log(err);
throw err;
}
}
return {
promise: run(),
abort: abort
}
}
So, to use this, you would have to change the way you call addOrdersToDB() (since it no longer returns just a promise) and you would have to capture the abort() function that it returns. Then, some other part of your code can call the abort() function and it will then flip the internal stop variable that will cause your do/while loop to stop any further iterations.
Note, this does not stop the asynchronous processing inside the current iteration of the do/while loop - it just stops any further iterations of the loop.
Note, I also changed your catch block so that it rethrows the error so that the caller will see if/when there was an error.
And, the resolved value of the function is the internal stop variable so the caller can see if the loop was aborted or not. A true resolved value means the loop was aborted and there was more work to do.
Here's an additional version of the function that creates more opportunities for it to stop between await operations within your function and within the loop. This still does not abort an individual database operation that may be in progress - you'd have to examine whether your database supports such an operation and, if so, how to use it.
function addOrdersToDB(limit) {
let stop = false;
function abort() {
stop = true;
}
async function run() {
try {
// Get latest order from DB
let latestOrd = await Order.findOne().sort('-order_number');
if (!stop) {
do {
// Get Shopify Orders
let orders = await getShopifyOrders(
latestOrd ? latestOrd.order_id : 0,
limit
);
latestOrd = orders[0] ? orders[orders.length - 1] : undefined;
if (stop) break;
// Update array with tracking status
let fullArray = await getTrack(orders);
if (stop) break;
// Add to DB
let ins = await Order.insertMany(fullArray, { ordered: false });
console.log(`Added ${ins.length} entries`);
} while (!stop && latestOrd);
}
// make resolved value be a boolean that indicates
// whether processing was stopped with more work still pending
return !!(latestOrd && stop);
} catch (err) {
// log and rethrow error so error gets propagated back to cller
console.log(err);
throw err;
}
}
return {
promise: run(),
abort: abort
}
}
for (let i = 0; i < 10; i++) {
const promise = new Promise((resolve, reject) => {
const timeout = Math.random() * 1000;
setTimeout(() => {
console.log(i);
}, timeout);
});
// TODO: Chain this promise to the previous one (maybe without having it running?)
}
The above will give the following random output:
6
9
4
8
5
1
7
2
3
0
The task is simple: Make sure each promise runs only after the other one (.then()).
For some reason, I couldn't find a way to do it.
I tried generator functions (yield), tried simple functions that return a promise, but at the end of the day it always comes down to the same problem: The loop is synchronous.
With async I'd simply use async.series().
How do you solve it?
As you already hinted in your question, your code creates all promises synchronously. Instead they should only be created at the time the preceding one resolves.
Secondly, each promise that is created with new Promise needs to be resolved with a call to resolve (or reject). This should be done when the timer expires. That will trigger any then callback you would have on that promise. And such a then callback (or await) is a necessity in order to implement the chain.
With those ingredients, there are several ways to perform this asynchronous chaining:
With a for loop that starts with an immediately resolving promise
With Array#reduce that starts with an immediately resolving promise
With a function that passes itself as resolution callback
With ECMAScript2017's async / await syntax
With ECMAScript2020's for await...of syntax
But let me first introduce a very useful, generic function.
Promisfying setTimeout
Using setTimeout is fine, but we actually need a promise that resolves when the timer expires. So let's create such a function: this is called promisifying a function, in this case we will promisify setTimeout. It will improve the readability of the code, and can be used for all of the above options:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
See a snippet and comments for each of the options below.
1. With for
You can use a for loop, but you must make sure it doesn't create all promises synchronously. Instead you create an initial immediately resolving promise, and then chain new promises as the previous ones resolve:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
for (let i = 0, p = Promise.resolve(); i < 10; i++) {
p = p.then(() => delay(Math.random() * 1000))
.then(() => console.log(i));
}
So this code creates one long chain of then calls. The variable p only serves to not lose track of that chain, and allow a next iteration of the loop to continue on the same chain. The callbacks will start executing after the synchronous loop has completed.
It is important that the then-callback returns the promise that delay() creates: this will ensure the asynchronous chaining.
2. With reduce
This is just a more functional approach to the previous strategy. You create an array with the same length as the chain you want to execute, and start out with an immediately resolving promise:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
[...Array(10)].reduce( (p, _, i) =>
p.then(() => delay(Math.random() * 1000))
.then(() => console.log(i))
, Promise.resolve() );
This is probably more useful when you actually have an array with data to be used in the promises.
3. With a function passing itself as resolution-callback
Here we create a function and call it immediately. It creates the first promise synchronously. When it resolves, the function is called again:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
(function loop(i) {
if (i >= 10) return; // all done
delay(Math.random() * 1000).then(() => {
console.log(i);
loop(i+1);
});
})(0);
This creates a function named loop, and at the very end of the code you can see it gets called immediately with argument 0. This is the counter, and the i argument. The function will create a new promise if that counter is still below 10, otherwise the chaining stops.
When delay() resolves, it will trigger the then callback which will call the function again.
4. With async/await
Modern JS engines support this syntax:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
(async function loop() {
for (let i = 0; i < 10; i++) {
await delay(Math.random() * 1000);
console.log(i);
}
})();
It may look strange, as it seems like the promises are created synchronously, but in reality the async function returns when it executes the first await. Every time an awaited promise resolves, the function's running context is restored, and proceeds after the await, until it encounters the next one, and so it continues until the loop finishes.
5. With for await...of
With EcmaScript 2020, the for await...of found its way to modern JavaScript engines. Although it does not really reduce code in this case, it allows to isolate the definition of the random interval chain from the actual iteration of it:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
async function * randomDelays(count, max) {
for (let i = 0; i < count; i++) yield delay(Math.random() * max).then(() => i);
}
(async function loop() {
for await (let i of randomDelays(10, 1000)) console.log(i);
})();
You can use async/await for this. I would explain more, but there's nothing really to it. It's just a regular for loop but I added the await keyword before the construction of your Promise
What I like about this is your Promise can resolve a normal value instead of having a side effect like your code (or other answers here) include. This gives you powers like in The Legend of Zelda: A Link to the Past where you can affect things in both the Light World and the Dark World – ie, you can easily work with data before/after the Promised data is available without having to resort to deeply nested functions, other unwieldy control structures, or stupid IIFEs.
// where DarkWorld is in the scary, unknown future
// where LightWorld is the world we saved from Ganondorf
LightWorld ... await DarkWorld
So here's what that will look like ...
async function someProcedure (n) {
for (let i = 0; i < n; i++) {
const t = Math.random() * 1000
const x = await new Promise(r => setTimeout(r, t, i))
console.log (i, x)
}
return 'done'
}
someProcedure(10)
.then(console.log)
.catch(console.error)
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
done
See how we don't have to deal with that bothersome .then call within our procedure? And async keyword will automatically ensure that a Promise is returned, so we can chain a .then call on the returned value. This sets us up for great success: run the sequence of n Promises, then do something important – like display a success/error message.
Based on the excellent answer by trincot, I wrote a reusable function that accepts a handler to run over each item in an array. The function itself returns a promise that allows you to wait until the loop has finished and the handler function that you pass may also return a promise.
loop(items, handler) : Promise
It took me some time to get it right, but I believe the following code will be usable in a lot of promise-looping situations.
Copy-paste ready code:
// SEE https://stackoverflow.com/a/46295049/286685
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
Usage
To use it, call it with the array to loop over as the first argument and the handler function as the second. Do not pass parameters for the third, fourth and fifth arguments, they are used internally.
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const items = ['one', 'two', 'three']
loop(items, item => {
console.info(item)
})
.then(() => console.info('Done!'))
Advanced use cases
Let's look at the handler function, nested loops and error handling.
handler(current, index, all)
The handler gets passed 3 arguments. The current item, the index of the current item and the complete array being looped over. If the handler function needs to do async work, it can return a promise and the loop function will wait for the promise to resolve before starting the next iteration. You can nest loop invocations and all works as expected.
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const tests = [
[],
['one', 'two'],
['A', 'B', 'C']
]
loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => {
console.info('Performing test ' + idx)
return loop(test, (testCase) => {
console.info(testCase)
})
.then(testNext)
.catch(testFailed)
}))
.then(() => console.info('All tests done'))
Error handling
Many promise-looping examples I looked at break down when an exception occurs. Getting this function to do the right thing was pretty tricky, but as far as I can tell it is working now. Make sure to add a catch handler to any inner loops and invoke the rejection function when it happens. E.g.:
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const tests = [
[],
['one', 'two'],
['A', 'B', 'C']
]
loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => {
console.info('Performing test ' + idx)
loop(test, (testCase) => {
if (idx == 2) throw new Error()
console.info(testCase)
})
.then(testNext)
.catch(testFailed) // <--- DON'T FORGET!!
}))
.then(() => console.error('Oops, test should have failed'))
.catch(e => console.info('Succesfully caught error: ', e))
.then(() => console.info('All tests done'))
UPDATE: NPM package
Since writing this answer, I turned the above code in an NPM package.
for-async
Install
npm install --save for-async
Import
var forAsync = require('for-async'); // Common JS, or
import forAsync from 'for-async';
Usage (async)
var arr = ['some', 'cool', 'array'];
forAsync(arr, function(item, idx){
return new Promise(function(resolve){
setTimeout(function(){
console.info(item, idx);
// Logs 3 lines: `some 0`, `cool 1`, `array 2`
resolve(); // <-- signals that this iteration is complete
}, 25); // delay 25 ms to make async
})
})
See the package readme for more details.
If you are limited to ES6, the best option is Promise all. Promise.all(array) also returns an array of promises after successfully executing all the promises in array argument.
Suppose, if you want to update many student records in the database, the following code demonstrates the concept of Promise.all in such case-
let promises = students.map((student, index) => {
//where students is a db object
student.rollNo = index + 1;
student.city = 'City Name';
//Update whatever information on student you want
return student.save();
});
Promise.all(promises).then(() => {
//All the save queries will be executed when .then is executed
//You can do further operations here after as all update operations are completed now
});
Map is just an example method for loop. You can also use for or forin or forEach loop. So the concept is pretty simple, start the loop in which you want to do bulk async operations. Push every such async operation statement in an array declared outside the scope of that loop. After the loop completes, execute the Promise all statement with the prepared array of such queries/promises as argument.
The basic concept is that the javascript loop is synchronous whereas database call is async and we use push method in loop that is also sync. So, the problem of asynchronous behavior doesn't occur inside the loop.
here's my 2 cents worth:
resuable function forpromise()
emulates a classic for loop
allows for early exit based on internal logic, returning a value
can collect an array of results passed into resolve/next/collect
defaults to start=0,increment=1
exceptions thrown inside loop are caught and passed to .catch()
function forpromise(lo, hi, st, res, fn) {
if (typeof res === 'function') {
fn = res;
res = undefined;
}
if (typeof hi === 'function') {
fn = hi;
hi = lo;
lo = 0;
st = 1;
}
if (typeof st === 'function') {
fn = st;
st = 1;
}
return new Promise(function(resolve, reject) {
(function loop(i) {
if (i >= hi) return resolve(res);
const promise = new Promise(function(nxt, brk) {
try {
fn(i, nxt, brk);
} catch (ouch) {
return reject(ouch);
}
});
promise.
catch (function(brkres) {
hi = lo - st;
resolve(brkres)
}).then(function(el) {
if (res) res.push(el);
loop(i + st)
});
})(lo);
});
}
//no result returned, just loop from 0 thru 9
forpromise(0, 10, function(i, next) {
console.log("iterating:", i);
next();
}).then(function() {
console.log("test result 1", arguments);
//shortform:no result returned, just loop from 0 thru 4
forpromise(5, function(i, next) {
console.log("counting:", i);
next();
}).then(function() {
console.log("test result 2", arguments);
//collect result array, even numbers only
forpromise(0, 10, 2, [], function(i, collect) {
console.log("adding item:", i);
collect("result-" + i);
}).then(function() {
console.log("test result 3", arguments);
//collect results, even numbers, break loop early with different result
forpromise(0, 10, 2, [], function(i, collect, break_) {
console.log("adding item:", i);
if (i === 8) return break_("ending early");
collect("result-" + i);
}).then(function() {
console.log("test result 4", arguments);
// collect results, but break loop on exception thrown, which we catch
forpromise(0, 10, 2, [], function(i, collect, break_) {
console.log("adding item:", i);
if (i === 4) throw new Error("failure inside loop");
collect("result-" + i);
}).then(function() {
console.log("test result 5", arguments);
}).
catch (function(err) {
console.log("caught in test 5:[Error ", err.message, "]");
});
});
});
});
});
In ES6, you should use 'for await':
(async () => {
for await (const num of asyncIterable) {
console.log(num);
}
// My action here
})();
For more information, see this for await...of.
I see the previous answers and feel confused. And I coded the following by the answers' inspiration. I think its logic is more obvious, I call the function to replace original for loop:
async function pointToCountry(world, data) { // Data is for loop array
if (data.length > 0) { // For condition
const da = data.shift(); // Get current data and modified data one row code
// Some business logic
msg = da.info
pointofView(world, da);
// Await the current task
await new Promise(r => setTimeout(_ => {
r() // Resolve and finish the current task
}, 5000))
// Call itself and enter the next loop
pointToCountry(world, data)
} else { // Business logic after all tasks
pointofView(world, { longitude: 0, latitude: 0 });
world.controls().autoRotate = true;
}
}
// This is my main function - calculate all project by city
const projectCity = async (req, res, next) => {
try {
let record = [];
let cityList = await Cityodel.find({active:true});
for (let j = 0; j < cityList.length; j++) {
let arr = [];
let projectList = await getProduct(cityList[j]._id)
arr.push({
_id:cityList[j]._id,
name:cityList[j].name,
projectList:projectList
})
record.push(arr);
}
return res.status(200).send({
status: CONSTANT.REQUESTED_CODES.SUCCESS,
result: record });
} catch (error) {
return res.status(400).json(UTILS.errorHandler(error));
}
};
async function getProduct(city){
let projectList = await ProjectModel.find({city:city});
return projectList;
}
I've created a snippet in Angular that loops a promise function indefinitely. You can start it, stop it, or restart it.
You basically need to recursively call the same method and await it's current process like so:
async autoloop(): Promise<void> {
if(this.running){
await this.runMe();
await this.autoloop();
}
return Promise.resolve();
}
JavaScript:
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
messages: string[] = [];
counter = 1;
running = false;
constructor() {
this.start();
}
onClick(): void {
this.running = !this.running;
if(this.running){
this.start();
}
else{
this.stop();
}
}
async onRestartClick(): Promise<void>{
await this.stop();
this.messages = [];
this.counter = 1;
this.start();
}
start(): void{
this.running = true;
this.autoloop();
}
async stop(): Promise<void>{
this.running = false;
await this.delay(1000);
}
async autoloop(): Promise<void> {
if(this.running){
await this.runMe();
await this.autoloop();
}
return Promise.resolve();
}
async runMe(): Promise<void> {
await this.delay(1000);
if(this.running){
this.messages.push(`Message ${this.counter++}`);
}
return Promise.resolve();
}
async delay(ms: number) {
await new Promise<void>((resolve) => setTimeout(() => resolve(), ms));
}
}
Html:
<h1>Endless looping a promise every 1 second</h1>
<button (click)="onClick()">Start / stop</button>
<button (click)="onRestartClick()">Restart</button>
<p *ngFor="let message of messages">
{{message}}
</p>
I'm developing a Google Chrome extension which collects data from two servers and sends it to another service. I don't understand how to make it asynchronous. The requests seem to work fine.
I searched Google for some explanations but just found some basic tutorials with timeouts. Also, the Product-Server accepts the Ajax request, the Deal-Server doesn't (CORS Error). So I used XMLHttpRequest.
document.addEventListener("DOMContentLoaded", function () {
var createButton = document.getElementById("createButton");
createButton.addEventListener("click", function () {
getProducts();
getDeals();
}, false)
function getProducts() {
var list = [];
chrome.tabs.getSelected(null, function (tab) {
var Url = parseDealIdToUrl(tab.url)
$.get(Url, function (data, status) {
const jsonData = JSON.parse(JSON.stringify(data))
const productArray = jsonData.data
productArray.forEach(product => {
productList.push(new Product(product.id, product.deal_id, product.product_id, product.name, product.item_price, product.quantity, product.duration, product.discount_percentage, product.currency, product.sum, product.sum_formatted))
});
})
});
}
function getDeals(maxPages = 1, currentPage = 1, akquises = []) {
var akquiseList = akquises;
if (currentPage <= maxPages) {
var Url = dealUrl + currentPage
var xhr = new XMLHttpRequest();
xhr.open("GET", Url, true);
xhr.setRequestHeader("Authorization", "Token token=")
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
const akquiseArray = JSON.parse(xhr.responseText);
akquiseArray.forEach(akquise => {
akquiseList.push(new Akquise(akquise.name, akquise.id))
});
getDeals(handlePagination(xhr.getResponseHeader("Link")), currentPage + 1, akquiseList)
}
}
xhr.send();
}
}
}, false)
I want to call both functions and wait till both Lists are filled, then send the Data to the Service. Any idea would help me!
I'm not quite sure what you mean by "make it asynchronous." As wOxxOm said, XMLHttpRequest is an asynchronous method. Do you mean you're not sure how to combine the results of multiple asynchronous operations? For the sake of this answer I'll assume that's the case.
Basic Asynchronicity
In order to break down how asynchronous functions work, let's look at a simplified example of your code. Below we have a main function that calls 2 different asynchronous functions. When you run this block you'll get a DONE message logged to the console, Async 1 complete logged after 1 second, and Async 2 complete logged 1 more second later.
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
(function main() {
doAsync1();
doAsync2();
console.log('DONE');
})()
function doAsync1() {
setTimeout(() => {
console.log('Async 1 complete');
}, 1000);
}
function doAsync2() {
setTimeout(() => {
console.log('Async 2 complete');
}, 2000)
}
The reason DONE is logged before the other statements is because doAsync1 and doAsync2 are asynchronous – it take a couple seconds for them to complete their work. When you call doAsync1() in main, the JS engine will step into the doAsync1 function and start executing lines. The first line is a setTimeout call. This function takes its first argument and schedules it for execution 1000 milliseconds later.
At this point the JS engine has done everything it can in doAsync1, so it steps out of that function and calls the next line, doAsync2. Again, doAsync2 schedules its callback for future execution and returns.
Next, the engine will execute the console.log line which makes DONE appear in the console.
1000 ms later, the callback scheduled by doAsync1 will run execute and log Async 1 complete to the console. Another 1000 ms later the callback scheduled by doAsync2 will log Async 2 complete.
Basic Callbacks
Now let's say doAsync1 and doAsync2 generate some data we want to use in main once both of complete. In JS, we traditionally use callbacks to get notified when some operation we're interested in completes.
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
function doAsync1(callback) {
setTimeout(() => {
console.log('Async 1 started');
const data = "Async 1 payload";
callback(data);
}, 1000);
}
function doAsync2(callback) {
setTimeout(() => {
console.log('Async 2 started');
const data = "Async 2 payload";
callback(data);
}, 2000);
}
(function main() {
const response = {};
doAsync1(handleAsync1);
doAsync2(handleAsync2);
function handleAsync1(data) {
response.async1 = data;
handleComplete();
}
function handleAsync2(data) {
response.async2 = data;
handleComplete();
}
function handleComplete() {
if (response.hasOwnProperty('async1') && response.hasOwnProperty('async2')) {
console.log('DONE', response);
}
}
})();
Promises
While this does the job, it's a bit verbose. Promises are an abstraction of one-time callbacks that makes it easier to chain blocks of work together.
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
// Promisified version of setTimeout
function timeout(duration) {
return new Promise(resolve => {
setTimeout(resolve, duration);
});
}
function doAsync1(callback) {
return timeout(1000).then(() => {
console.log('Async 1 started');
const data = "Async 1 payload";
return data;
});
}
function doAsync2(callback) {
return timeout(2000).then(() => {
console.log('Async 2 started');
const data = "Async 2 payload";
return data;
});
}
(function main() {
// Starts both doAsync1 and doAsync2 at the same time. Once both complete, the
// promise will resolve with both response values.
Promise.all([
doAsync1(),
doAsync2()
]).then(response => {
console.log('DONE', response[0], response[1]);
});
})();
Async/Await
With ES2016 we gained 2 new keywords: async and await. These keywords are essentially syntactic sugar that make it a little easier to work with promises in JavaScript. For demo purposes, let's take a look at our Promises example converted to async/await.
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
function timeout(duration) {
return new Promise(resolve => {
setTimeout(resolve, duration);
});
}
async function doAsync1(callback) {
await timeout(1000);
console.log('Async 1 started');
const data = "Async 1 payload";
return data;
}
async function doAsync1(callback) {
await timeout(2000);
console.log('Async 2 started');
const data = "Async 2 payload";
return data;
}
(async function main() {
const response = await Promise.all([
doAsync1(),
doAsync2()
]);
console.log('DONE', response[0], response[1]);
})();
For a much deeper dive into async functions, check out Async functions - making promises friendly by Jake Archibald.
Use the following code snippet to add async/await functions to chrome extension.
Usage: put the following code snippet to the beginning of both your content script and background script.
/**
* Usage:
* let cookies = await asyncfy(chrome.cookies.getAll)({ url })
* let tabs = await asyncfy(chrome.tabs.query)({active: true, currentWindow: true})
*
* #param fn A function that takes one or more parameters, and the last parameter is a callback which has one or more parameter. The simplest one is chrome.management.getSelf
* #returns {function(...[*]): Promise<any>} Return one value if the results array has only one element, else return the whole results array
*/
let asyncfy = fn => (...args) => {
return new Promise((resolve, reject) => {
fn(...args, (...results) => {
let { lastError } = chrome.runtime
if (typeof lastError !== 'undefined') reject(lastError);
else results.length == 1 ? resolve(results[0]) : resolve(results);
});
});
}
let isObject = function(obj) {
var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
};
// provide async method to all methods which have one callback.
let handler = {
get: function(target, prop, receiver) {
let value = target[prop]
let type = typeof value
if(type !== 'undefined') { // including null, false
if( type === 'function') return value.bind(target); // correct the this for the functions, since we've substituted the original object to the proxy object
return value;
}
if(prop.endsWith('Async')){
let key = prop.replace(/Async$/, '')
let method=target[key]
let asyncMethod = asyncfy(method.bind(target));
return asyncMethod;
}
}
}
// proxy every leaf object
let asyncfyObj = handler => obj => Object.getOwnPropertyNames(obj)
.filter(prop => isObject(obj[prop]))
.forEach(prop => obj[prop] = new Proxy(obj[prop], handler))
// intercept the getters of all object in chrome member
asyncfyObj(handler)(chrome)
asyncfyObj(handler)(chrome.storage)
// console.log(`active tab: ${JSON.stringify(await getActiveTabAsync())}`)
let getActiveTabAsync = async () => {
let tabs = await chrome.tabs.queryAsync({active: true, currentWindow: true});
return (tabs && tabs.length > 0) ? tabs[0] : null;
}
// chrome.storage.local.set({ foo: 'bar' });
// console.log(`foo: ${await getLocalStorageAsync('foo')}`)
let getLocalStorageAsync = async key => ( await chrome.storage.local.getAsync([key]) ) [key];
Testing: put the following snippet in your background script and make sure related permissions have been added to the manifest.json.
(async () => {
console.log(cookies: ${JSON.stringify(await asyncfy(chrome.cookies.getAll)({ url: 'https://www.stackoverflow.com/' }))})
console.log(active tab: ${JSON.stringify(await getActiveTabAsync())})
chrome.storage.local.set({ 'foo': 'bar'});
console.log(storage: ${await getLocalStorageAsync('foo')})
console.log(extension install type: ${( await chrome.management.getSelfAsync() )['installType']})
} )()
my gist
So I am writing a script to execute some things for me automatically on a site. so far I have figured everything out but I am doing some things incorrectly and just want to figure out a better way to do it.
I have a for loop that will send requests to an API via a fetch. The number of loops will be based on a variable. I want to be able to process the response from each fetch as they come in and if any of them ever shows up with a result of true I want it to stop my for loop from continuing. Currently the way I have it working is it will refresh the page (which will stop my loop) but this is very inefficient. I just needed to get it done somehow.
As for the synchronous/asynchronous i want it to send the requests regardless of the response, until the response is a true then I want it to stop sending requests. It is okay if I have a few requests over which is what happens now with my page refresh method.
Im open to suggestions and to revamp the whole thing (im just learning things as i go and didnt even know what js was 2 months ago. but for all the things I have figured out how to do, I cant seem to grasp this concept.)
I've tried naming the loop and breaking # name, tried returning a break, and nothing ive tried has managed to work. The only thing that has is a page refresh which is one way to stop a loop i guess.
var reqs = 5000;
xId = 970;
xSym = mySymbol;
var looper = async function () {
for (var start = 1; start < reqs; start++) {
await new Promise(resolve => {
setTimeout(function () {
//This is essentially what I need to loop over and over.
//but not any faster then 200 ms per request.
fetch("https://example.com/api/send", {"credentials":"include","body":`{\"xactionId\":\"${xId}\",\"symbol\":\"${xSym}\"}`,"method":"POST","mode":"cors"})
.then(resp => resp.json())
.then(json => {
if(json.test.result === true) {
console.log(json.test.hash, json.test.number);
//
//This is where I want to be able to stop the loop
//If any of the completed results
//come back true to have it discontinue the Looper
//
//currently I do a window.location.reload();
//
}})
.catch(err => console.log(err));
resolve(true);
}, 200);
});
}
return true;
}
looper().then(function(){
console.log("Got a match!");
});
And just in case anyone needs the responses i get back from server.
{
"result": {
"hash": "dbbb42b293",
"result": false,
"isHigh": false,
"number": 4993,
"threshold": 3,
"chance": 0.03,
"nonce": 2194375,
"created": 1554150935
},
"dailyFree": false,
"status": null,
"user": {
"hash": "aabbccdd8f",
"level": 300,
"username": "user",
"requests": 4440936,
"nonce": 2194376,
"volume": "11.10794076",
"lockedBalance": null,
"session": {
"requests": 5,
"volume": "0.000004"
}
}
}
I want to be able to stop the for loop, in the looper async function based on a result in the second .then after the fetch POST request.
Also, I am wondering if the above is possible while also having the fetch request in its own external function so that i can call it from other places in the code as well.
RESOLUTION: I used the last option that #Bergi suggested. Thanks for the help!
my final code looks like this:
var reqs = 5000;
xId = 970;
xSym = mySymbol;
function delay(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
async function looper() {
var run = true;
for (var start = 1; run && start < reqs; start++) {
await delay(200);
fetch("https://example.com/api/send", {"credentials":"include","body":`{\"xactionId\":\"${xId}\",\"symbol\":\"${xSym}\"}`,"method":"POST","mode":"cors"})
.then(resp => resp.json())
.then(json => {
if (json.test.result === true) {
console.log(json.test.hash, json.test.number);
run = false;
}
});
}
return true;
}
looper().then(function(){
console.log("DONE!")
});
Avoid the Promise constructor antipattern! You should use
function delay(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
and not use the Promise constructor elsewhere, especially wrapping around other promise calls. I guess you are looking for
async function looper() {
for (var start = 1; start < reqs; start++) {
await delay(200);
const resp = await fetch("https://example.com/api/send", {"credentials":"include","body":`{\"xactionId\":\"${xId}\",\"symbol\":\"${xSym}\"}`,"method":"POST","mode":"cors"});
const json = await resp.json();
if (json.test.result === true) {
console.log(json.test.hash, json.test.number);
break;
// ^^^^^
}
}
return true;
}
Or maybe have the delay(200) and the fetch run in parallel, so that you are waiting at minimum 200ms not additionally to the time that fetch takes:
async function looper() {
for (var start = 1; start < reqs; start++) {
const [, json] = await Promise.all([
await delay(200),
fetch("https://example.com/api/send", {"credentials":"include","body":`{\"xactionId\":\"${xId}\",\"symbol\":\"${xSym}\"}`,"method":"POST","mode":"cors"}).then(resp => resp.json()),
]);
if (json.test.result === true) {
console.log(json.test.hash, json.test.number);
break;
// ^^^^^
}
}
return true;
}
If you really wanted to fire off a fetch request every 200ms, you cannot use await here. You'd have to use a boolean variable in the looping condition that checks whether any of the already received responses wants the loop to stop:
async function looper() {
var run = true;
for (var start = 1; run && start < reqs; start++) {
// ^^^^^^
await delay(200);
fetch("https://example.com/api/send", {"credentials":"include","body":`{\"xactionId\":\"${xId}\",\"symbol\":\"${xSym}\"}`,"method":"POST","mode":"cors"})
.then(resp => resp.json())
.then(json => {
if (json.test.result === true) {
console.log(json.test.hash, json.test.number);
run = false;
// ^^^^^^^^^^^^
}
})
.catch(err => console.log(err));
}
return true;
}
In your looper you have the loop and you do a await:
var looper = async function () {
for (var start = 1; start < reqs; start++) {
await new Promise(resolve => { ... })
}
}
Await can be used to assign the result of your promise to a variable. This way you can control your loop outside the promises.
var looper = async function () {
for (var start = 1; start < reqs; start++) {
const continue = await new Promise(resolve => {
setTimeout(function () {
//This is essentially what I need to loop over and over.
//but not any faster then 200 ms per request.
fetch("https://example.com/api/send", {"credentials":"include","body":`{\"xactionId\":\"${xId}\",\"symbol\":\"${xSym}\"}`,"method":"POST","mode":"cors"})
.then(resp => resp.json())
.then(json => {
if(json.test.result === true) {
console.log(json.test.hash, json.test.number);
resolve(false); //break
}})
.catch(err => console.log(err));
resolve(true); //continue
}, 200);
if (!continue) break;
})
}
}
Basically you have different contexts in here, one of them is the loop, the other is the Promise (and the setTimeout callback). Returning from the setTimeout would not resolve your promise nor return you anything useful outside. What we do here is that we await the promise to resolve and we recover a boolean that tells us if we should break the loop. Then, in the context of the loop we decide to either break or continue.
I need to call an api to get a status every 2 seconds if the response is running and first return when response is either complete or failed, or until 30 seconds have passed and the function times out.
This is what I have now which works, but I am sure it can be done much more efficient, but I simply can't figure it out at this point:
const getStatus = async (processId) => {
try {
const response = await fetch(`example.com/api/getStatus/${processId}`);
const status = await response.json();
return await status;
} catch(err) {
// handle error
}
}
Inside another async function using getStatus():
randomFunction = async () => {
let status = null;
let tries = 0;
let stop = false;
while (tries <= 15 && !stop) {
try {
status = await getStatus('some-process-id');
if (status === 'complete') {
stop = true;
// do something outside of loop
}
if (status === 'failed') {
stop = true;
throw Error(status);
}
if (tries === 15) {
stop = true;
throw Error('Request timed out');
}
} catch (err) {
// handle error
}
const delay = time => new Promise(resolve => setTimeout(() => resolve(), time));
if (tries < 15) {
await delay(2000);
}
tries++;
}
}
I would prefer to handle the looping inside getStatus() and in a more readable format, but is it possible?
EDIT:
I tried a solution that looks better and seems to work as I expect, see it here:
https://gist.github.com/AntonBramsen/6cec0faade032dfa3c175b7d291e07bd
Let me know if parts of the solution contains any solutions that are bad practice.
Your question is for javascript. Unfortunately I don't drink coffee, I can only give you the code in C#. But I guess you get the gist and can figure out how to translate this into java
Let's do this as a generic function:
You have a function that is called every TimeSpan, and you want to stop calling this function whenever the function returns true, you want to cancel, whenever some maximum time has passed.
For this maximum time I use a CancellationToken, this allows you to cancel processing for more reasons than timeout. For instance, because the operator wants to close the program.
TapiResult CallApi<TapiResult> <Func<TapiResult> apiCall,
Func<TapiResult, bool> stopCriterion,
CancellationToken cancellationToken)
{
TapiResult apiResult = apiCall;
while (!stopCriterion(apiResult))
{
cancellationToken.ThrowIfCancellationRequested();
Task.Delay(delayTime, cancellationToken).Wait;
apiResult = apiCall;
}
return apiResult;
}
ApiCall is the Api function to call. The return value is a TApiResult. In your case the status is your TApiResult
StopCriterion is a function with input ApiResult and output a boolean that is true when the function must stop. In your case this is when status equals complete or failed
CancellationToken is the Token you can get from a CancellationTokenSource. Whenever you want the procedure to stop processing, just tell the CancellationTokenSource, and the function will stop with a CancellationException
Suppose this is your Api:
Status MyApiCall(int x, string y) {...}
Then the usage is:
Timespan maxProcessTime = TimeSpan.FromSeconds(45);
var cancellationTokenSource = new CancellationTokenSource();
// tell the cancellationTokenSource to stop processing afer maxProcessTime:
cancellationTokenSource.CancelAfter(maxProcessTime);
// Start processing
Status resultAfterProcessing = CallApi<Status>(
() => MyApiCall (3, "Hello World!"), // The Api function to call repeatedly
// it returns a Status
(status) => status == complete // stop criterion: becomes true
|| status == failed, // when status complete or failed
cancellationTokenSource.Token); // get a token from the token source
TODO: add try / catch for CancellationException, and process what should be done if the task cancels
The function will stop as soon as the stopCriterion becomes true, or when the CancellationTokenSource cancels. This will automatically be done after maxTimeOut. However, if you want to stop earlier, for instance because you want to stop the program:
cancellationTokenSource.Cancel();