Suppose I have two asynchronous functions, A and B, which are independent to each other.
What I am trying to do is that execute these functions sequentially multiple time as shown below
A -> B -> A -> B -> A -> ...
B waits until A finishes and vice versa.
The following is what I have done so far and I know it is not going to work the way I want.
function A() {
var promise = new Promise...
...
return promise;
}
function B() {
var promise = new Promise...
...
return promise;
}
for(var i=0; i<200; i++) {
var p1 = A();
p1.then(() => {
var p2 = B();
// ----
}
}
How should I change the code?
You're headed the right way, but you need to keep chaining the thens. You generally start with a pre-resolved promise from Promise.resolve() then add to the chain using then, keeping each new promise:
let p = Promise.resolve();
for (var i=0; i<200; i++) {
p = p.then(A).then(B);
}
p.then(() => {
console.log("All done");
});
Live Example (with 20 instead of 200):
let counterA = 0;
let counterB = 0;
function A() {
var promise = new Promise(resolve => {
setTimeout(() => {
++counterA;
console.log("A done (" + counterA + ")");
resolve();
}, 100);
});
return promise;
}
function B() {
var promise = new Promise(resolve => {
setTimeout(() => {
++counterB;
console.log("B done (" + counterB + ")");
resolve();
}, 100);
});
return promise;
}
let p = Promise.resolve();
for (var i=0; i<20; i++) {
p = p.then(A).then(B);
}
p.then(() => {
console.log("All done");
});
.as-console-wrapper {
max-height: 100% !important;
}
(In real code, you'd have a .catch as well to handle rejections, of course; or you'd be returning the last promise to other code that would handle them.)
You can chain calls with chained .then() to make sure they are called after the previous ones are done.
let cA = cB = 0;
function A() {
return new Promise(resolve => {
setTimeout(() => {
resolve("A " + ++cA);
console.log("A done");
}, 200);
});
}
function B() {
return new Promise(resolve => {
setTimeout(() => {
resolve("B " + ++cB);
console.log("B done");
}, 300);
});
}
function callConsecutive(times) {
if (times == 0) return Promise.resolve([]);
times--;
const a = A(),
b = a.then(B),
c = b.then(() => { return callConsecutive(times) });
return Promise.all([a, b, c]).then(([r1,r2,r3]) => Promise.resolve([r1,r2,...r3]));
}
callConsecutive(5).then(results => { console.log(results); })
You can do it with a recursive function
function seqAB(count) {
if(!count) Promise.resolve();
var p1 = A();
p1.then(() => {
var p2 = B();
p2.then(() => {
seqAB(count - 1);
})
})
}
seqAB(200);
Related
At the end I included two example promises to check that the code works and it does work:
async function slower(arr: Promise<any> []): Promise<[number ,any] | undefined | any[]> {
/* here we check if one is rejected in the question i solved
we have to return or print something if one of the two
promises has rejected its just an extra
*/
let firstisresolved = false;
let secondisresolved = false;
try {
await arr[0].then(function(){
firstisresolved = true;
});
await arr[1].then(function(){
secondisresolved = true;
});
} catch {
console.log("something went bad");
}
/* we get an array of tow promises and we basically check which
one is faster-using promise.race()() and promise.all
*/
if (!(secondisresolved && firstisresolved )) {
console.log("there is one promise got rejected");
return;
} else {
let num =1;
try {
/* here its very important to run promise.race() before
running promise.all() because if you run all and then
race the promises would be already done so you it will
return the first one any way so remeber to run
promise.race() before promise.all()
*/
let a = await Promise.race(arr);
let b = await Promise.all(arr);
if(a === b[0]){
num = 0;
}
// console.log(num);
// console.log(a);
let c = [num,a]
return c;
} catch(err) {
console.log("there is a problem")
}
}
}
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 5000, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 6000, 'two');
});
console.log(slower([promise1,promise2]));
I want to call web api at some interval through javascript/angular js 1.x. I have to do this because web api that I am calling restricts the number of calls per 300 second.
Please find below code that I am trying to make it work.
RecordList is list of objects that I need to pass with webapi call.
setDelay is function that adds the delay to each call with setTimeout function.
Inside the setTimeout function, I am calling webapi and it is creating the record successfully. I want a object or event or piece of code which gives me that all the records has been successfully created or not. That means how to know that all the promises inside the setTimeout function has been resolved or not. I know Promise.all and $q.all uses but they do not seem to be working with setTimeout function. Could you suggest something for this?
var waitInterval = 300;
var promiseArray = [];
function setDelay(obj, s) {
setTimeout(function() {
var SomePromise = $http.post('odataURI', obj).then(function success(result) {
//resolve(result);
});
promiseArray.push(SomePromise);
}, waitInterval * s);
}
for (var s = 1; s <= RecordList.length; s++) {
setDelay(RecordList[s - 1], s);
}
You can use a recursive function to run the next promise after one finished.
function post(n) {
return Promise.resolve(n);
}
function setDelay(n, milliseconds) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
post(n).then(function(res) {
resolve(res);
});
}, milliseconds)
})
}
function call(list, delay = 1000, index = 0, result = []) {
setDelay(list[index], delay).then(
function(res) {
console.log(res);
result.push(res);
if (index == list.length - 1) {
console.log('Result: ', result); // You can do something here
return result;
} else {
return call(list, delay, index + 1, result);
}
}
);
}
var RecordList = [1, 2, 3, 4, 5];
call(RecordList);
You can use async await for the same
var waitInterval = 300;
var promiseArray = [];
async FunctionOfLoop() {
var dataMain;
for (var s = 1; s <= RecordList.length; s++) {
dataMain = await setDelay(RecordList[s - 1], s); // here you will get the data after api consume successfully.
}
}
function setDelay(obj, s) {
return new Promise ((resolve, reject) => {
setTimeout(function() {
var SomePromise = $http.post('odataURI', obj).then(function success(result) {
resolve(result);
});
}, waitInterval * s);
});
}
I hope it helps you out.
Without Recursion also its possible,
var waitInterval = 300;
var promiseArray = [];
var tick =0;
function setDelay(obj, s) {
setTimeout(function() {
var SomePromise = Promise.resolve(obj);
SomePromise.then(function success(result) {
tick ++;
console.log(result);
if ( tick === RecordList.length) {
Promise.all(promiseArray).then((res) => {
console.log(res);
});
}
});
promiseArray.push(SomePromise);
}, waitInterval * s);
}
var RecordList = [1,2,3,4,5];
for (var s = 1; s <= RecordList.length; s++) {
setDelay(RecordList[s - 1], s);
}
Well I understand that one would have to return a promise, and not the result of a promise to pass promises around.
However I seem to be unable to implement this, say I have a member method like:
CreateNextBatch() {
this.orders.clear();
let maxNum = this.maxNum;
let counter = this.orderCounter;
let CreateThem = (counter, r) => {
if (r >= 0) {
//Order.find() finds an entry in a database
Order
.find({orderNr: counter.fullNumber()})
.then(function(orders) {
console.log("created order " + counter.fullNumber().toString());
let num = r;
if (orders.length === 0) {
this.OpenOrder(counter.fullNumber());
//adds order to this.orders
num -= 1;
}
counter.nextNumber();
return CreateThem(counter, num);
}.bind(this))
.catch (function (err){
console.log(err);
return false;
});
} else {
return true;
}
};
return () => {CreateThem(counter, maxNum);};
}
Basically it creates orders in a recursive fashion, terminating after finding this.MaxRequests empty spots and then puts them together in a list under this.orders
Now I called this function by:
initialLoader.CreateNextBatch().then(function (success) {
console.log(success);
console.log("loaded");
initialLoader.initializeBatch();
});
However this fails:
TypeError: initialLoader.CreateNextBatch(...).then is not a function
at LoadExternDatabase...
Why isn't this working? What am I not understanding yet?
Edit: I've also tried to replace the return by a new promise:
return new Promise((resolve, reject) => {
CreateThem(counter, maxRequests);
resolve();
});
However this executes the resolve immediatelly, instead of waiting for CreateThem to complete. Nor directly the function by return CreateThem.bind(this, counter, maxRequests);
You should resolve a promise after the asynchronous operation done.
For example:
function test() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// Resolved here!
// Because asynchronous function `setTimeout` ends here
resolve();
}, 1000);
})
}
In your code, if r >= 0, it will find an entry in database again until r less than 0, so it ends at else block:
if (r >= 0) {
// ...
} else {
// ends here
}
Maybe you could change your code to:
CreateNextBatch() {
return new Promise((resolve, reject) => {
this.orders.clear();
let maxNum = this.maxNum;
let counter = this.orderCounter;
let CreateThem = (counter, r) => {
if (r >= 0) {
//Order.find() finds an entry in a database
Order
.find({orderNr: counter.fullNumber()})
.then(function(orders) {
console.log("created order " + counter.fullNumber().toString());
let num = r;
if (orders.length === 0) {
this.OpenOrder(counter.fullNumber());
//adds order to this.orders
num -= 1;
}
counter.nextNumber();
return CreateThem(counter, num);
}.bind(this))
.catch (function (err){
console.log(err);
reject(err);
});
} else {
resolve(true);
}
};
});
// return () => {CreateThem(counter, maxNum);};
}
return new Promise((resolve, reject) => {
globalVarKeep = resolve;
globalVarBreak = reject;
});
Before that you would have had some code waiting for a promise or an event that says: -
CreateThem(counter, maxRequests);
globalVarKeep();
or
CreateThem(counter, maxRequests).then(globalVarKeep);
If that's appropriate.
The function initialLoader.CreateNextBatch() returns a function, not a promise, you should do: initialLoader.CreateNextBatch()()
The function CreateThem can return true if this.maxNum >= 0 and a promise of true or false depending on the recursion. But doesn't allways guaranteed return a promise. And () => {CreateThem(counter, maxNum);}; doesn't return anything either. You could try the following:
CreateNextBatch() {
this.orders.clear();
let maxNum = this.maxNum;
let counter = this.orderCounter;
let CreateThem = (counter, r) => {
if (r >= 0) {
//Order.find() finds an entry in a database
//you can return a promise here
return Order
.find({orderNr: counter.fullNumber()})
.then(function(orders) {
console.log("created order " + counter.fullNumber().toString());
let num = r;
if (orders.length === 0) {
this.OpenOrder(counter.fullNumber());
//adds order to this.orders
num -= 1;
}
counter.nextNumber();
return CreateThem(counter, num);
}.bind(this))
.catch (function (err){
console.log(err);
return false;
});
} else {
return Promise.resolve(true);
}
};
//you are not returning CreateThem here
return () => {return CreateThem(counter, maxNum);};
}
In following code with 100000 records recursiveFnReturnsPromiseV1 executes fine while recursiveFnReturnsPromiseV2 fails with out of stack exception. The only difference between two is the way promises are being recursed. In the v1, the recursion is within a "then" of the first promise while in v2, the recursion is within the original promise itself. What's the difference?
let aValues = Array(100000);
recursiveFnReturnsPromiseV1(aValues, 0).then(function() {
let p = document.createElement('div');
p.innerText = 'recursiveFnReturnsPromiseV1 finished';
document.getElementById('v1').appendChild(p);
}, function() {
let p = document.createElement('div');
p.innerText = 'recursiveFnReturnsPromiseV1 failed';
document.getElementById('v1').appendChild(p);
})
recursiveFnReturnsPromiseV2(aValues, 0)
.then(function() {
let p = document.createElement('div');
p.innerText = 'recursiveFnReturnsPromiseV2 finished';
document.getElementById('v2').appendChild(p);
}, function() {
let p = document.createElement('div');
p.innerText = 'recursiveFnReturnsPromiseV2 failed';
document.getElementById('v2').appendChild(p);
})
function recursiveFnReturnsPromiseV1(pValues, ix) {
if (pValues.length <= ix)
return Promise.resolve();
return new Promise(function(c, e) {
document.getElementById('v1').innerText = ix + 'v1' + Date.now();
c();
}).then(function() {
return recursiveFnReturnsPromiseV1(pValues, ++ix);
})
}
function recursiveFnReturnsPromiseV2(pValues, ix) {
if (pValues.length <= ix)
return Promise.resolve();
return new Promise(function(c, e) {
document.getElementById('v2').innerText = ix + 'v2' + Date.now();
recursiveFnReturnsPromiseV2(pValues, ++ix).then(function() {
c();
}, function(err) {
e()
});
})
}
<div id='v1'></div>
<div id='v2'></div>
Lets strip away the Promise constructor antipattern, and use a simple Promise.resolve instead. Your functions now become
function recursiveFnReturnsPromiseV1(pValues, ix) {
if (pValues.length <= ix)
return Promise.resolve();
let p = document.createElement('div');
p.innerText = ix + 'v1' + Date.now();
document.getElementById('v1').appendChild(p);
return Promise.resolve().then(function() {
return recursiveFnReturnsPromiseV1(pValues, ++ix);
})
}
function recursiveFnReturnsPromiseV2(pValues, ix) {
if (pValues.length <= ix)
return Promise.resolve();
let p = document.createElement('div');
p.innerText = ix + 'v2' + Date.now();
document.getElementById('v2').appendChild(p);
return Promise.resolve(recursiveFnReturnsPromiseV2(pValues, ++ix));
}
It should be obvious by now why the second function overflows the stack.
Why doesn't the first as well? Because the recursive call is inside an asynchronous then callback, which means it starts later (after the outer call returned) with a fresh call stack.
I am trying solve the following challenge where I have to write a function triggerActions that passes a callback into the processAction, and produces the output:
"Process Action 1"
"Process Action 2"
...
"Process Action n"
Here is the provided function:
function processAction(i, callback) {
setTimeout(function() {
callback("Processed Action " + i);
}, Math.random()*1000);
}
Function to code:
function triggerActions(count) {
}
Note that the code for processAction cannot be altered. I was thinking of using a Promise but I'm not sure how. I believe the setTimeout is actually synchronous so I don't know if async/await would work.
My attempt:
triggerActions = count => {
let promises = [];
for(let i=1; i<=count; i++) {
promises.push(new Promise( (resolve, reject) => processAction(i, str => resolve(str))));
}
let results = []
promises.forEach( promise => Promise.resolve(promise).then( async res => results.push(await res)));
return results;
}
I kind of like short and sweet:
var n = 5
var stop = 1
triggerActions = function(text) {
if (text) console.log(text)
if (stop <= n){
processAction(stop++, triggerActions)
}
}
triggerActions()
P.S
It occurred to me that perhaps you are only allowed to provide a function which means the stop variable declaration outside the function is a problem. It makes it a little more verbose, but you can wrap it all inside the function like this:
function triggerActions(stop) {
var rFn = (text) => {
if (text) console.log(text)
if (stop <= n){
processAction(stop++, rFn)
}
}
rFn()
}
triggerActions(1)
There you go:
// Your unaltered function
function processAction(i, callback) {
setTimeout(function() {
callback("Processed Action " + i);
}, Math.random()*1000);
}
// The function you want to implement
function triggerActions(count) {
var triggerAction = function (i) { // Local function to process the given action number:
if (i <= count) { // More actions to execute?
processAction(i, function (text) {// Process current action number and pass a callback in parameter
console.log(text); // Write the result of processAction
triggerAction(i + 1); // Trigger the next action
}); //
} //
}
triggerAction(1); // First things first: start at action one
}
// Call the function
triggerActions(10);
The original poster's instinct to use promises was correct.
The two solutions above may work but because each call to triggerActions() has to wait for the delay to elapse before the next call can be made, this is considerably slow.
Maybe this is what you want but here's an optimized solution using promises and Promise.all():
const processAction = (i, callback) => {
setTimeout(function() {
callback("Processed Action " + i);
}, Math.random()*1000);
}
const triggerActions = (n) => {
const promises = [];
const generatePromise = (i) => {
return new Promise((resolve, reject) => {
processAction(i, resolve);
});
}
for (let i = 1; i <= n; i += 1) {
promises.push(generatePromise(i));
}
Promise.all(promises)
.then((strings) => strings.forEach((string) => console.log(string)));
}
triggerActions(10);
To compare the performance differences, try running the two approaches side by side.
Here's my solution:
function processAction(i, callback) {
setTimeout(function() {
callback("Processed Action " + i);
}, Math.random()*1000);
}
// Function to code:
function triggerActions(count) {
const asyncArr = [];
for (let i = 1; i <= count; i++) {
asyncArr.push(new Promise(resolve => processAction(i, resolve)));
}
Promise.all(asyncArr).then((vals) => {
vals.forEach((val) => console.log(val))
});
}
triggerActions(5);
Here is my solution using Promise.all:
function triggerActions(count) {
const promises = range(count).map(
i => new Promise(resolve => processAction(i, resolve))
);
Promise.all(promises).then(results => {
results.forEach(result => console.log(result));
});
}
// Generates an array from 1...n
function range(n) {
return Array.from({ length: n }, (_, i) => i + 1);
}
The requirements are that the function ‘processAction’ should remain unchanged and invoked in a batch.
For this I have used the util.promisify function that takes a function and converts it into a promise. A promise can be invoked in a batch with Promise.all.
Another requirement is that the callback should output “Processed Action i” where i is a number. The anonymous function ‘func’ has been defined to do this.
The triggerActions function takes a number, x, creates an array of numbers containing indices from 0 to x and then invokes a count of x asynchronous functions simultaneously.
const {promisify} = require('util');
function processAction(i, callback) {
setTimeout(function() {
callback("Processed Action " + i);
}, Math.random()*1000);
}
const func = (param1) => console.log(param1);
const promisifyedProcessAction = promisify(processAction);
async function triggerActions(count) {
const arr = [];
for(let i = 0; i < count;)
arr.push(++i);
await Promise.all(
arr.map((value) => promisifyedProcessAction(value,func)));
}
triggerActions(5);
Here's an overview of all the possible approaches:
Callback-based:
Sequential:
function triggerActions(count) {
;(function recur(i = 0) {
processAction(i, (data) => {
console.log(data)
if (i < count) {
recur(i + 1)
}
})
})()
}
Concurrent
function triggerActions(count) {
const data = Array.from({ length: count })
for (let i = 0; i < count; i++) {
processAction(i, (result) => {
data[i] = result
count--
if (count == 0) {
for (const x of data) {
console.log(x)
}
}
})
}
}
Promise-based:
We can use this function to make processAction async:
function processActionP(i) {
return new Promise((res) => processAction(i, res))
}
Sequential:
async function triggerActions(count) {
for (let i = 0; i < count; i++) {
const data = await processActionP(i)
console.log(data)
}
}
Concurrent:
async function triggerActions(count) {
const data = await Promise.all(
Array.from({ length: count }, (_, i) => processActionP(i)),
)
for (const x of data) {
console.log(x)
}
}
Concurrent, using lodash/fp
const _ = require('lodash/fp')
const triggerActions = _.pipe(
_.range(0),
_.map(processActionP),
Promise.all.bind(Promise),
data => data.then(
_.each(console.log)
),
)