How to create a sequential promise loop with variable function calls - javascript

I need to somehow loop over the work array passed to _start then
for each of the items in the array, I need to somehow call the corresponding function with the same name.
I don't have control over the number of items in work the array or the number of items, I do know that there will always be a corresponding function.
I don't want to call all the functions at the same time, once the first function resolves after 3 seconds, I then want to call the second function, once the second function resolves after 3 seconds I then want to call the third function. Once the third function resolves after another 3 seconds I want to call _done().
In this example each function takes 3 seconds to complete _done wont gete called for 9 seconds.
function _start(data){
// Insert some kinda native magic loop
}
function _one(){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(1);
}, 3000);
})
};
function _two(){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(2);
}, 3000);
})
};
function _done(){
console.log('All done in 9 seconds)
}
(function(){
var work = ['_one', '_two', '_two'];
_start(work);
})();

Given the order is dicated by the array, you can use reduce to aggregate the promises into a chain
const _start = (...actions) => {
return actions.reduce((chain, action) => {
const func = this[action];
return chain.then(() => func());
}, Promise.resolve());
}
...
_start('_one', '_two', '_three').then(() => console.log('All done'));
See it in action - the example appends an extra then to the chain just to output any results from the promises (probably outwith the scope of this question but something you may have to consider if getting data back is required).
Update
Can see you intend on invoking _start from a different context in which the functions are declared, this is fine but you need to make sure you set the correct context before hand i.e.
const self = this;
(function() {
_start.bind(self)('_one', '_two', '_two');
})();

A function which creates a promise which sleeps:
const sleep = n => () => new Promise(resolve => setTimeout(resolve, n));
A function which sleeps after some input promise:
const sleepAfter = n => p => p.then(sleep(n));
A function which chains a bunch of promises, represented by functions:
const chain = (...promises) => promises.reduce((ret, promise) => ret.then(promise),
Promise.resolve());
Run a bunch of functions yielding promises, sleeping in between:
const _start = promises => chain(promises.map(sleepAfter(3000)));
Now just:
_start(_one, _two, _three).then(_done);

Try using this:
_one().then((firstResponse) {
return_two();
}) .then((secondResponse) => {
*second and first respone are already done*
});

Use promises then
_one().then((responseOne) => {
return _two();
}).then((responseTwo) => {
// _one & _two are done
});

Related

Resolve promise on array push - javascript

I'm attempting to define a function that returns a promise. The promise should resolve when a given array is set (push()).
To do this I'm attempting to use a Proxy object (influenced by this):
let a = []
;(async function(){
const observe = array => new Promise(resolve =>
new Proxy(array, {
set(array, key, val) {
array[key] = val;
resolve();
}
}));
while(true){
await observe(a);
console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:",`${a.pop()}`);
}
})(a);
;(async function(){
await new Promise(resolve => timerID = setTimeout(resolve, 2000))
a.push('ʕ·͡ᴥ·ʔ');
a.push('¯\(°_o)/¯ ')
})(a)
I can't see why this doesn't work. Does anyone have any idea?
More generally, what is a good way to have a promise resolve on push to an array?
The problems with your attempt:
you invoke .push on the original array, not the proxied one. Where you create the proxy, it is returned to no-one: any reference to it is lost (and will be garbage collected).
The code following after the line with await will execute asynchronously, so after all of your push calls have already executed. That means that console.log will execute when the array already has two elements. Promises are thus not the right tool for what you want, as the resolution of a promise can only be acted upon when all other synchronous code has run to completion. To get notifications during the execution synchronously, you need a synchronous solution, while promises are based on asynchronous execution.
Just to complete the answer, I provide here a simple synchronous callback solution:
function observed(array, cb) {
return new Proxy(array, {
set(array, key, val) {
array[key] = val;
if (!isNaN(key)) cb(); // now it is synchronous
return true;
}
});
}
let a = observed([], () =>
console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:", `${a.pop()}`)
);
a.push('ʕ·͡ᴥ·ʔ');
a.push('¯\(°_o)/¯ ');
As noted before: promises are not the right tool when you need synchronous code execution.
When each push is executed asynchronously
You can use promises, if you are sure that each push happens in a separate task, where the promise job queue is processed in between every pair of push calls.
For instance, if you make each push call as part of an input event handler, or as the callback for a setTimeout timer, then it is possible:
function observed(array) {
let resolve = () => null; // dummy
let proxy = new Proxy(array, {
set(array, key, val) {
array[key] = val;
if (!isNaN(key)) resolve();
return true;
}
});
proxy.observe = () => new Promise(r => resolve = r);
return proxy;
}
let a = observed([]);
(async () => {
while (true) {
await a.observe();
console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:",`${a.pop()}`);
}
})();
setTimeout(() => a.push('ʕ·͡ᴥ·ʔ'), 100);
setTimeout(() => a.push('¯\(°_o)/¯ '), 100);

How to enforce promise in array loop

I'm trying to get the basics of how to implement promises and Promise.All within a given array loop, for example, from a firebase query, which can perform any number of actions within the loop, and then I have access to the results later. I can't seem to get the correct syntax, logic . below is a sample method in which I'd like to delay insert some items into an array and then have access to the array after the loop for processing.
I've researched different methods on Stackoverfllow. Some assign a promises = the snap.forEach loop and then somehow resolve this after completion. Others are creating a promise inside the loop. In my example below, I'm just using the settimeout to delay/create an async process.
testfirebaseData = () => {
let channels =[];
var promises=[];
firebase.database().ref('challengeResponses').child(day).once('value', snap => {
snap.forEach(channelChild => {
promises.push(new Promise((resolve) => {
setTimeout(() => {
channels.push(channelChild.key);
resolve(channels)
},1000)
})
)
})
}).then (() => {
Promise.all(promises).then(() => {
console.log(channels[0])
})
}
}
I would expect the output of the above to show the first element in the "channels" array, but it's always coming back with "undefined"..as I clearly am not processing the promises correctly. What am i missing?
Here is sample example using your code style. Instead of objects within the array I've put numbers;
https://jsfiddle.net/th3vecmg/4/
var promises = [];
let channels =[];
[1, 2, 3, 4, 5].forEach((elm, i) => {
promises.push(new Promise((resolve) => {
setTimeout(() => {
channels.push(elm)
resolve(elm);
}, 1000)
}))
});
console.log(channels[0]) // gives undefined
Promise.all(promises).then(() => {
console.log(channels[0]) // gives 1
})
You should not be getting undefined inside the then because it's waiting for the promise to execute. You will only get undefined it you are trying to display the result outside the then scope
The code doesn't make really sense since you are displaying a global variable inside the then rather then relying on the output of the promise
If you want to delay execution by 1 then do some multiplication in the timeArgument of the setTimeout...
Your new Promise(...) should wrap the firebase call as that is the async operation. And not be in the callback. There is no async operations going on inside the callback just plain array push which is a sync op
var promise = new Promise((resolve)=>{
firebase.database().ref('challengeResponses').child(day).once('value', snap => {
var channels=[];
snap.forEach(channelChild => {
channels.push(channelChild.key);
});
resolve(channels);
});
});
promise.then((channels)=>{
console.log(channels);
});
If you want a delay on the whole operation:
var promise = new Promise((resolve)=>{
firebase.database().ref('challengeResponses').child(day).once('value', snap => {
var channels=[];
setTimeout(()=>{
snap.forEach(channelChild => {
channels.push(channelChild.key);
});
resolve(channels);
},1000);
});
});
To simulate nested async operations you would add the promises to an array like you were but resolve Promises.all() in the outer promise and resolve your data in the inner promises:
var promise = new Promise((resolve)=>{
firebase.database().ref('challengeResponses').child(day).once('value', snap => {
var snapPromises=[];
snap.forEach(channelChild => {
snapPromises.push(new Promise((snapresolve)=>{
setTimeout(()=>{
snapresolve(channelChild.key);
},1000)
});
});
resolve(Promise.all(snapPromises));
});
});

Javascript: horizontal chaining promises without multiple then calls

Is there a short inbuilt way to do horizontal promise chaining?
The usual way to chain steps is:
(async()=>1)().then(_=>{
//etc..
return _+_
}).then(_=>{
//etc..
return _*_
}).then(_=>{
//etc..
return alert(_) // alert 4
})
So I use a helper to avoid repeating then:
F=(...args)=>{
let p=args[0]
for(let x=1; x<args.length;++x){
p=p.then(args[x])
}
}
F((async()=>1)(), _=>{
//etc..
return _+_
}, _=>{
//etc..
return _*_
}, _=>{
//etc..
return alert(_)
})
Another variation that overloads prototype and allows for second argument of then:
Promise.prototype.thenAll = function(...args){
let p = this;
args.forEach(_=>p=p.then(_[0], _[1]))
}
;(async()=>1)().thenAll([_=>{
//etc..
return _+_
}], [_=>{
//etc..
return _*_
}], [_=>{
//etc..
return alert(_)
}])
But is there an inbuilt way to do something akin to these?
As others have said there is no built in way to do this. The following code might be of interest though.
There is a concept known as 'lifting' whereby you transform a function into one that works on wrapped values. In this case promises. It would look something like this:
const lift = (fn) => (promise) => promise.then(fn);
There is also the idea of chaining together a list of functions, composing them with one another. Like this:
const chain = (...fns) => (value) => fns.reduce((result, fn) => fn(result), value)
Together, these tools allow you to rewrite your code as:
chain(
lift(_=>++_),
lift(_=>_*=_),
lift(_=>alert(_))
)((async()=>1)())
Which alerts 4 as expected.
I'm also a little confused by your use of ++_ and _*=_ because they imply you wish to mutate the variable. Because of how your code is structured it would be a better display of intent to use _+1 and _*_
Using Array.reduce(), you can combine the series of functions into a promise chain using the following static function:
function series (initial, ...callbacks) {
return callbacks.reduce(
(chain, callback) => chain.then(callback),
Promise.resolve(initial)
)
}
series(1, _=>_+1, _=>_*_, alert)
For convenience, you could define this as Promise.series() like this:
Object.defineProperty(Promise, 'series', {
configurable: true,
value: function series (initial, ...callbacks) { ... },
writable: true
})
Lastly, in ES2017, you could alternatively use async / await to write it like this:
async function series(initial, ...callbacks) {
let value = await initial
for (const callback of callbacks) {
value = await callback(value)
}
return value
}
series(1, _=>_+1, _=>_*_, alert)
You can use rest parameter to pass N functions to a function which return a Promise to be called in sequence with the previous Promise value set to parameter passed to the current function call until no elements remain in array using async/await and repeated scheduling of the same procedure
const a = n => new Promise(resolve =>
setTimeout(resolve, Math.floor(Math.random() * 1000), ++n));
const b = n => new Promise(resolve =>
setTimeout(resolve, Math.floor(Math.random() * 1000), n * n));
const F = async(n, ...fns) => {
try {
while (fns.length) n = await fns.shift()(n);
alert(n);
} catch (err) {
throw err
}
return n
}
F(1, a, b)
.then(n => console.log(n))
.catch(err => console.error(err));
Given that the code at Question is synchronous you can use the code at Question
((_) => (_++, _*=_, alert(_)) )(1)
Or use async/await
(async(_) => (_ = await _, _++, _ *= _, alert(_)))(Promise.resolve(1))

Promise not waiting on one another

So my understanding of promises lead me to believe that my other promises would run one after another in my then chain but I'm doing something wrong here.
The code I'm using currently is as follows
const mainPromise = () => Promise.resolve(
console.log('spinner spinning...')
...spinner code.... //this is omitted code
);
const getData = () => Promise.resolve(
someObj.getProducts('data.json')
);
const updateProduct = () => Promise.resolve(
setTimeout(()=>{
someObj.updateProductHTML()
}, 0)
);
const updateDom = () => {
setTimeout(()=>{
someObj.updateDOM()
}, 0)
};
and my promise chain
mainPromise()
.then(getData)
.then(updateProduct)
.then(updateDom)
;
They seem to be initially running in order but the Ajax call I have in getProducts also has a for loop worker to build my array of objects and is finishing after all my .thens run.
I am attempting to at least have my data call and worker finish before updateProduct and updateDOM runs
--- UPDATE ---
ok so with the revised promises set up as such as per suggestions in the comments and Samanime's answer
const mainPromise = () => Promise.resolve(
console.log('spinner spinning...')
);
const getData = () => new Promise( resolve => {
console.log('getData');
someObj.getProducts('data.json');
resolve();
}
);
const updateProduct = () => new Promise( resolve =>{
console.log('updateProduct');
someObj.updateProductHTML();
resolve();
});
//execute promise chain
mainPromise()
.then(getData)
.then(updateProduct)
.then(page.updateDOM)
;
I updated the promises to not immediately resolve and am attempting to call resolve after I call my functions ( although I'm uneasy as to if resolve will be called before or after these functions ).
Unfortunately, I'm still getting the same behavior. I've added console logs to my functions as well as my promises and I'm getting this list back
log.spinner spinning
log.getData
log.updateProduct
log.A log from the function updateProduct calls
log.48 product objects created (for loop worker in my getProducts function)
log.Data retrieved and assigned
the last two logs would ideally be called after getData
None of the calls or functions outside of the ones provided are return promises, I'm working on legacy code and I'm moving away from the setTimeout trick as well as my results weren't consistent.
--UPDATE 2 --
The problem I'm having is known as Forking/Splitting. I just need to figure out chaining specifically to fix my issue.
-- FINAL --
this is what I ended up working out
// execute promise chain
mainPromise()
.then(getData);
//the timeout is a little hack to ensure the sequence is kept
mainPromise()
.then(() => {
setTimeout(() => {
myObj.updateProductHTML();
myObj.updateDOM();
}, 0);
});
apparently .then(foo).then(bar) just runs foo and bar at the same time
seems to be working ok right but I feel like somethings not right with it still.
I believe it's because Promise.resolve() doesn't do quite what you think it does.
Promise.resolve() creates a new Promise and resolves it immediately using the value of what it's given. Things like setTimeout return their id (an integer) immediately, so they aren't doing what you want. Your getProducts() is probably an async call, so it may be returning null or something as well (if it's returning a Promise or returns the value synchronously, then it's fine).
You're better off writing a normal Promise and calling resolve() at the appropriate time.
const mainPromise = () => Promise.resolve(
console.log('spinner spinning...')
...spinner code....
);
// Assuming it's already returning a Promise or synchronous response. If it isn't, then deal with it like the setTimeout ones below.
const getData = () => someObj.getProducts('data.json')
const updateProduct = () => new Promise(resolve => {
setTimeout(()=>{
someObj.updateProductHTML();
resolve();
}, 0)
});
// You don't NEED to in your example since it's at the end of the chain, but you probably want to wrap this too in case you add to the chain.
const updateDom = () => new Promise(resolve => {
setTimeout(()=>{
someObj.updateDOM();
resolve();
}, 0)
});

How do you wrap setTimeout in a promise [duplicate]

This question already has answers here:
What is the JavaScript version of sleep()?
(91 answers)
Closed 1 year ago.
I am trying to run a test suite for an object that returns a promise. I want to chain several actions together with short timeouts between them. I thought that a "then" call which returned a promise would wait for the promise to be fulfilled before firing the next chained then call.
I created a function
function promiseTimeout (time) {
return new Promise(function(resolve,reject){
setTimeout(function(){resolve(time);},time);
});
};
to try and wrap setTimeout in a Promise.
Then in my test suite, I am calling something like this ...
it('should restore state when browser back button is used',function(done){
r.domOK().then(function(){
xh.fire('akc-route-change','/user/4/profile/new');
}).then(promiseTimeout(2000)).then(function(t){
xu.fire('akc-route-change','/user/6');
}).then(promiseTimeout(10)).then(function(t){
expect(xu.params[0]).to.equal(6);
history.back();
}).then(promiseTimeout(10)).then(function(){
expect(xu.params[0]).to.equal(4);
done();
});
});
I can put a breakpoint on the first xh.fire call and a second one on the xu.fire call and would have expected a two second gap when a continues from the first breakpoint to the second.
Instead it reaches the second breakpoint immediately, and the value of t at that point is undefined.
What am I doing wrong?
TL;DR - you've wrapped setTimeout in a promise properly, the issue is you are using it improperly
.then(promiseTimeout(2000)).then
will not do what you expect. The "signature" for .then is then(functionResolved, functionRejected)
A promise’s then method accepts two arguments:
promise.then(onFulfilled, onRejected)
Both onFulfilled and onRejected are optional arguments:
If onFulfilled is not a function, it must be ignored.
If onRejected is not a function, it must be ignored.
source: https://promisesaplus.com/#point-21
You are not passing a function to then
Consider the way you are doing it:
Promise.resolve('hello')
.then(promiseTimeout(2000))
.then(console.log.bind(console))
vs how it should be done:
Promise.resolve('hello').then(function() {
return promiseTimeout(2000)
}).then(console.log.bind(console))
The first outputs 'hello' immediately
The second outputs 2000 after 2 seconds
Therefore, you should be doing:
it('should restore state when browser back button is used', function(done) {
r.domOK().then(function() {
xh.fire('akc-route-change', '/user/4/profile/new');
}).then(function() {
return promiseTimeout(2000);
}).then(function(t) {
xu.fire('akc-route-change', '/user/6');
}).then(function() {
return promiseTimeout(10);
}).then(function(t) {
expect(xu.params[0]).to.equal(6);
history.back();
}).then(function() {
return promiseTimeout(10);
}).then(function() {
expect(xu.params[0]).to.equal(4);
done();
});
});
Alternatively:
it('should restore state when browser back button is used', function(done) {
r.domOK().then(function() {
xh.fire('akc-route-change', '/user/4/profile/new');
}).then(promiseTimeout.bind(null, 2000)
).then(function(t) {
xu.fire('akc-route-change', '/user/6');
}).then(promiseTimeout.bind(null, 10)
).then(function(t) {
expect(xu.params[0]).to.equal(6);
history.back();
}).then(promiseTimeout.bind(null, 10)
).then(function() {
expect(xu.params[0]).to.equal(4);
done();
});
});
EDIT: March 2019
Over the years, things have changed a lot - arrow notation makes this even easier
Firstly, I would define promiseTimeout differently
const promiseTimeout = time => () => new Promise(resolve => setTimeout(resolve, time, time));
The above returns a function that can be called to create a "promise delay" and resolves to the time (length of delay). Thinking about this, I can't see why that would be very useful, rather I'd:
const promiseTimeout = time => result => new Promise(resolve => setTimeout(resolve, time, result));
The above would resolve to the result of the previous promise (far more useful)
But it's a function that returns a function, so the rest of the ORIGINAL code could be left unchanged. The thing about the original code, however, is that no values are needed to be passed down the .then chain, so, even simpler
const promiseTimeout = time => () => new Promise(resolve => setTimeout(resolve, time));
and the original code in the question's it block can now be used unchanged
it('should restore state when browser back button is used',function(done){
r.domOK().then(function(){
xh.fire('akc-route-change','/user/4/profile/new');
}).then(promiseTimeout(2000)).then(function(){
xu.fire('akc-route-change','/user/6');
}).then(promiseTimeout(10)).then(function(){
expect(xu.params[0]).to.equal(6);
history.back();
}).then(promiseTimeout(10)).then(function(){
expect(xu.params[0]).to.equal(4);
done();
});
});
To make a timeout which works as you want, write a function which takes a delay, and returns a function suitable for passing to then.
function timeout(ms) {
return () => new Promise(resolve => setTimeout(resolve, ms));
}
Use it like this:
Promise.resolve() . then(timeout(1000)) . then(() => console.log("got here"););
However, it is likely that you will want to access the resolved value of the promise leading into the timeout. In that case, arrange for the function created by timeout() to pass through the value:
function timeout(ms) {
return value => new Promise(resolve => setTimeout(() => resolve(value), ms));
}
Use it like this:
Promise.resolve(42) . then(timeout(1000)) . then(value => console.log(value));
This has already been answered above, but I feel this could be done easily with:
const setTimeoutPromise = ms => new Promise(resolve => setTimeout(resolve, ms))
setTimeoutProise function accept wait time in ms and passes it down to the setTimeout function. Once the wait time is over, the resolve method passed down to the promise is executed.
Which could be used like this:
setTimeoutPromise(3000).then(doSomething)
await new Promise((resolve, reject)=>{
// wait for 50 ms.
setTimeout(function(){resolve()}, 50);
});
console.log("This will appear after waiting for 50 ms");
This can be used in an async function and the execution will wait till given interval.
Another approach for adding delays to Promises without having to predefine or import a helper function (that I personally like best) is to extend the property of the Promise constructor:
Promise.prototype.delay = function (ms) {
return new Promise(resolve => {
window.setTimeout(this.then.bind(this, resolve), ms);
});
}
I'm leaving out the reject callback since this is meant to always resolve.
DEMO
Promise.prototype.delay = function(ms) {
console.log(`[log] Fetching data in ${ms / 1000} second(s)...`);
return new Promise(resolve => {
window.setTimeout(this.then.bind(this, resolve), ms);
});
}
document.getElementById('fetch').onclick = function() {
const duration = 1000 * document.querySelector('#duration[type="number"]').value;
// Promise.resolve() returns a Promise
// and this simply simulates some long-running background processes
// so we can add some delays on it
Promise
.resolve('Some data from server.')
.delay(duration)
.then(console.log);
}
<div>
<input id="duration" type="number" value="3" />
<button id="fetch">Fetch data from server</button>
</div>
Or if you need it to also be .catch()-able, here is when you need the second argument (reject). Note that the catch() handling will also occur after the delay:
Promise.prototype.delay = function(ms) {
console.log(`[log] Fetching data in ${ms / 1000} second(s)...`);
return new Promise((resolve, reject) => {
window.setTimeout(() => {
this.then(resolve).catch(reject);
}, ms);
});
}
document.getElementById('fetch').onclick = function() {
const duration = 1000 * document.querySelector('#duration[type="number"]').value;
Promise
.reject('Some data from server.')
.delay(duration)
.then(console.log)
.catch(console.log); // Promise rejection or failures will always end up here
}
<div>
<input id="duration" type="number" value="3" />
<button id="fetch">Fetch data from server</button>
</div>

Categories

Resources