Obviously, given a list l and an function f that returns a promise, I could do this:
Promise.all(l.map(f));
The hard part is, I need to map each element, in order. That is, the mapping of the first element must be resolved before the the next one is even started. I want to prevent any parallelism.
I have an idea how to do this, which I will give as an answer, but I am not sure it's a good answer.
Edit: some people are under the impression that since Javascript is itself single-threaded, parallelism is not possible in Javascript.
Consider the following code:
const delay = t => new Promise(resolve => setTimeout(resolve, t));
mapAsync([3000, 2000, 1000], delay).then(n => console.log('beep: ' + n));
A naïve implementation of mapAsync() would cause "beep" to be printed out once a second for three seconds -- with the numbers in ascending order -- but a correct one would space the beeps out increasingly over six seconds, with the number in descending orders.
For a more practical example, imagine a function that invoked fetch() and was called on an array of thousands of elements.
Further Edit:
Somebody didn't believe me, so here is the Fiddle.
const mapAsync = (l, f) => new Promise((resolve, reject) => {
const results = [];
const recur = () => {
if (results.length < l.length) {
f(l[results.length]).then(v => {
results.push(v);
recur();
}).catch(reject);
} else {
resolve(results);
}
};
recur();
});
EDIT: Tholle's remark led me to this far more elegant and (I hope) anti-pattern-free solution:
const mapAsync = (l, f) => {
const recur = index =>
index < l.length
? f(l[index]).then(car => recur(index + 1).then(cdr => [car].concat(cdr)))
: Promise.resolve([]);
return recur(0);
};
FURTHER EDIT:
The appropriately named Try-catch-finally suggest an even neater implementation, using reduce. Further improvements welcome.
const mapAsync2 = (l, f) =>
l.reduce(
(promise, item) =>
promise.then(results =>
f(item).then(result => results.concat([result]))),
Promise.resolve([])
);
Rather than coding the logic yourself I'd suggest using async.js for this. Since you're dealing with promises use the promisified async-q library: https://www.npmjs.com/package/async-q (note: the documentation is much easier to read on github:https://github.com/dbushong/async-q)
What you need is mapSeries:
async.mapSeries(l,f).then(function (result) {
// result is guaranteed to be in the correct order
});
Note that the arguments passed to f is hardcoded as f(item, index, arr). If your function accept different arguments you can always wrap it up in another function to reorder the arguments:
async.mapSeries(l,function(x,idx,l){
return f(x); // must return a promise
}).then(function (result) {
// result is guaranteed to be in the correct order
});
You don't need to do this if your function accepts only one argument.
You can also just use the original callback based async.js:
async.mapSeries(l,function(x,idx,l){
function (cb) {
f(x).then(function(result){
cb(null, result); // pass result as second argument,
// first argument is error
});
}
},function (err, result) {
// result is guaranteed to be in the correct order
});
You can't use map() on its own since you should be able to handle the resolution of the previous Promise. There was a good example of using reduce() for sequencing Promises in an Google article.
reduce() allowes you to "chain" the Promise of the current item with the Promise of the previous item. To start the chain, you pass a resolved Promise as initial value to reduce().
Assume l as the input data and async() to modify the data asynchronously. It will just multiply the input data by 10.
var l = [1, 2, 3 ,4];
function async(data) {
console.log("call with ", data);
return new Promise((resolve, reject) => {
setTimeout(() => { console.log("resolve", data); resolve(data * 10); }, 1000);
});
}
This is the relevant code (its function is inline-commented)
// Reduce the inout data into a Promise chain
l.reduce(function(sequencePromise, inValue) {
/* For the first item sequencePromise will resolve with the value of the
* Promise.resolve() call passed to reduce(), for all other items it's
* the previous promise that was returned by the handler in the next line.
*/
return sequencePromise.then(function(responseValues) {
/* responseValues is an array, initially it's the empty value passed to
* reduce(), for subsequent calls it's the concat()enation result.
*
* Call async with the current inValue.
*/
return async(inValue).then((outValue) => {
/* and concat the outValue to the
* sequence array and return it. The next item will receive that new
* array as value to the resolver of sequencePromise.
*/
return responseValues.concat([outValue]);
});
});
}, Promise.resolve([]) /* Start with a resolved Promise */ ).then(function(responseValues){
console.log(responseValues);
});
The console will finally log
Array [ 10, 20, 30, 40 ]
Related
I am looping over an array to update its values using returned value from called function which internally calls an asynchronous function.
I need to handle asynchronous function in synchronous way which is not being directly called. This is replication of scenario.
function condition(){
// Code of this function is not accessible to me.
return new Promise(function(resolve, reject){
setTimeout(function(){
if(parseInt(Math.random() * 100) % 2){
resolve(true);
}
else{
reject(false)
}
}, 1000)
});
}
async function delayIncrease(value){
var choice = await condition();
if(choice) { return ++value; }
else { return --value; }
}
// Start calling functions
dataArr = [1,2,3,4,5];
for(var i in dataArr){
dataArr[i] = delayIncrease(dataArr[i]);
}
If possible, I would like to have the solution in above structure mentioned.
I have achieved the desired result by adding other function and passing "index" + "new_value" as parameters. This function directly modifies original array and produces desired result. Working example.
function condition(){
// Code of this function is not accessible to me.
return new Promise(function(resolve, reject){
setTimeout(function(){
if(parseInt(Math.random() * 100) % 2){
resolve(true);
}
else{
reject(false)
}
}, 1000)
});
}
function delayIncrease(value, index){
condition().then(
function(){ updateData(++value, index) },
function(){ updateData(--value, index) }
)
}
function updateData(value, index){
dataArr[index] = value;
}
dataArr = [1,2,3,4,5];
for(var i in dataArr){
dataArr[i] = delayIncrease(dataArr[i], i);
}
Please provide solution for this requirement in best possible way. Possible solution in Angular 4 way is also appriciated. I thought of writing it in normal JavaScript form as Observables behave nearly same.
I followed this Medium page and http://exploringjs.com
Your condition function does not really fulfill the promise with either true or false, it does randomly fulfill or reject the promise. Instead of branching on a boolean, you will need to catch that "error":
async function delayIncrease(value) {
try {
await condition();
return ++value;
} catch(e) {
return --value;
}
}
You could do something like this:
var condition = async () =>
(parseInt(Math.random() * 100) % 2)
? true
: false
var delayIncrease = async (value) =>
(await condition())
? ++value
: --value
var dataArr = [1, 2, 3, 4, 5];
// Start calling functions
Promise.all(
dataArr.map(
delayIncrease
)
)
.then(
resolve => console.log("results:",resolve)
,reject => console.warn("rejected:",reject)
)
Once something is async you have to make the entire call stack prior to that function async. If a function calls an async function that that function returns an async value and so does the one calling it and calling it and calling it ...
More info on javascript async and why can be found here.
Since the example provided doesn't have any async api's in there you don't need to do it async:
var condition = () =>
(parseInt(Math.random() * 100) % 2)
? true
: false
var delayIncrease = (value) =>
(condition())
? ++value
: --value
var dataArr = [1, 2, 3, 4, 5];
// Start calling functions
dataArr.map(
delayIncrease
)
[update]
When you mutate an array of objects and cosole.log it you may not see the values as they actually were when you log it but you see the values as they are right now (this is a "bug" in console.log).
Consider the following:
var i = -1,arr=[];
while(++i<1){
arr[i]={};
arr[i]["name"+i]=i
}
var process = (index) =>
arr[index]["name"+index]++;
arr.forEach(
(item,index) =>
Promise.resolve(index)
.then(process)
);
console.log("obj at the moment you are looking at it:",arr)
console.log("obj at the moment it is logged:",JSON.stringify(arr))
When you expand obj at the moment you are looking at it you see that name0 property of the first element changed to 1.
However; look at obj at the moment it is logged: and see the actual value of the first element in the array. It has name0 of 0.
You may think that the that code runs asynchronous functions in a synchronous way by mutating the object(s) in an array, but you actually experience a "bug" in console.log
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
});
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))
New to promises; consider the case where there is promiseA() and promiseB(a) which depends on the first result, and I want to collect results from both and perform a third action doSomething(a, b):
Style A (closure/nesting)
promiseA().then(function (resultA) {
return (promiseB(resultA).then(function (resultB) {
doSomething(resultA, resultB);
}));
});
Style B (return value/chaining)
promiseA().then(function (resultA) {
return Promise.all([resultA, promiseB(resultA)]);
}).spread(function (resultA, resultB) {
doSomething(resultA, resultB);
});
As far as I can tell, these are equivalent:
Same sequencing constraint between promiseA and promiseB
Final promise returns undefined
Final promise is rejected if promiseA or promiseB are rejected, or doSomething throws.
As a matter of style, Style B reduces indentation (pyramid of doom).
However, Style B is more difficult to refactor. If I need to introduce an intermediate promiseA2(a) and doSomething(a, a2, b), I need to modify 3 lines (Promise.all, spread, doSomething), which can lead to mistakes (accidental swapping etc), while with Style A I only to modify 1 line (doSomething) and the variable name makes it clear which result it is. In large projects, this may be significant.
Are there other non-functional trade-offs between the two styles? More/less memory allocation in one vs the other? More/fewer turns around the event loop? Better/worse stack traces on exceptions?
I think the non-functional trade-offs between the two methods are not so important: the second method has some overhead in creating the array, and spreading the corresponding results, and it will create one more promise. However in an asynchronous flow, all this is negligible in my opinion.
Your major concern seems to be the ease of refactoring.
For that I would suggest to use an array of functions, and reduce over it:
[promiseA, promiseB, doSomething].reduce( (prom, f) =>
prom.then( (res = []) => ( f(...res) || prom).then( [].concat.bind(res) ) )
, Promise.resolve() );
// Sample functions
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function promiseA() {
console.log('promiseA()');
return wait(500).then(_ => 13);
}
function promiseB(a) {
console.log('promiseB(' + a + ')');
return wait(500).then(_ => a + 2);
}
function doSomething(a, b) {
console.log('doSomething(' + a + ',' + b + ')');
}
The idea is that the next function in the then callback chain gets all of the previous results passed as arguments. So if you want to inject a promise-returning function in the chain, it is a matter of inserting it in the array. Still, you would need to pay attention to the arguments that are passed along: they are cumulative in this solution, so that doSomething is not an exception to the rule.
If on the other hand, you only want doSomething to get all results, and only pass the most recent result to each of the intermediate functions, then the code would look like this:
[promiseA, promiseB].reduce( (prom, f) =>
prom.then( (res = []) => f(...res.slice(-1)).then( [].concat.bind(res) ) )
, Promise.resolve() ).then(res => doSomething(...res));
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function promiseA() {
console.log('promiseA()');
return wait(100).then(_ => 13);
}
function promiseB(a) {
console.log('promiseB(' + a + ')');
return wait(100).then(_ => a + 2);
}
function doSomething(a, b) {
console.log('doSomething(' + a + ',' + b + ')');
}
I'm trying to write a function that doesn't return it's value until a Promise inside the function has resolved. Here's a simplified example of what I'm trying to do.
'use strict';
function get(db, id) {
let p = db.command('GET', 'group:${id}');
p.then(g => {
g.memberNames = [];
g.members.forEach(id => {
User.get(db, id)
.then(profile => g.memberNames.push(profile.name))
.catch(err => reject(err));
});
return g;
});
}
It's a function that requests a group id and returns the data for that group. Along the way it's also throwing in the user's names into the data structure to display their names instead of their user id. My issue is that this is running asynchronously and will skip over the .then callbacks. By the time it gets to return g none of the callbacks have been called and g.memberNames is still empty. Is there a way to make the function wait to return g until all callbacks have been called?
I've seen a lot of stuff about await. Is this necessary here? It's highly undesired to add other libraries to my project.
Since your operation to return all the profile names is also async you should return a Promise fulfilled when all the other async operations complete (or reject when one of them is rejected) done with Promise.all
function get(db, id) {
let p = db.command('GET', 'group:${id}');
return p.then(g => {
return Promise.all(g.members.map(id => {
// NOTE: id is shadowing the outer function id parameter
return User.get(db, id).then(profile => profile.name)
})
})
}
Is there a way to make the function wait to return g until all callbacks have been called?
Yes, there is. But you should change your way of thinking from "wait until all the callbacks have been called" to "wait until all the promises are fulfilled". And indeed, the Promise.all function trivially does that - it takes an array of promises and returns a new promise that resolves with an array of the results.
In your case, it would be
function get(db, id) {
return db.command('GET', 'group:${id}').then(g => {
var promises = g.members.map(id => {
// ^^^ never use `forEach`
return User.get(db, id).then(profile => profile.name);
// ^^^^^^ a promise for the name of that profile
});
//
return Promise.all(promises).then(names => {
// ^^^^^^^^^^ fulfills with an array of the names
g.memberNames = names;
return g;
});
});
}