I've just started learning this amazing stuff. I can't figure out how to get values from an array of promises. Here's where am at:
const one = new Promise(resolve => {
setTimeout(() => {
resolve(1);
}, 1000);
})
const two = new Promise(resolve => {
setTimeout(() => {
resolve(2);
}, 2000);
})
const observable = Rx.Observable.from([one, two]);
observable.subscribe(v => console.log(v));
I get in console:
Promise { <pending> }
Promise { <pending> }
I'd like to get:
Result as an array of values [1,2]
Result as individual values in order of promise resolution 1,2
So, basically I want to emulate:
Promise.all([one, two])
Promise.resolve(1), Promise.resolve(2)
Static method Observable.from() emits each item in the array so what you have right now will just emit two Promise objects:
You're dealing with so called Higher-order Observables (aka Observables emitting Observables). This is in RxJS 5 easily solvable with concatAll or mergeAll depending on whether you care about the order they are specified or they can be collected as the resolve.
RxJS 5 treats Observables, Promises, iterators, array (and array like objects) the same way. This means we use your Promises just like they were Observables.
I'm using mergeAll here to show that the second Promise finished first even though they're defined in the opposite order [one, two].
const one = new Promise(resolve => {
setTimeout(() => {
resolve(1);
}, 1000);
})
const two = new Promise(resolve => {
setTimeout(() => {
resolve(2);
}, 500);
})
// Result as individual values in order of promise resolution 2,1
Rx.Observable.from([one, two])
.mergeAll()
.subscribe(v => console.log('mergeAll: ' + v));
// Result as an array of values [2,1]
Rx.Observable.from([one, two])
.concatAll()
.toArray()
.subscribe(v => console.log(v));
See live demo: https://jsbin.com/tigidon/4/edit?js,console
This prints to console:
mergeAll: 2
mergeAll: 1
[2, 1]
Related
I have following scenario:
Multiple http requests will be executed:
const sourceOne = http.get(obj1.product_id);
const sourceTwo = http.get(obj2.product_id);
const sourceThree = http.get(obj3.product_id);
const sourceFour = http.get(obj4.product_id);
can i somehow using Promises or Observables to:
Execute all of the requests at the same time
Update state when one of them is resolved, for example:
sourceThree, finish first - update state on the client
sourceFour, complete after sourceThree - update state on the client
All of the possible solutions that i can found are always wait for all of the requests to be completed before updating the state, any ideas?
You could make use of merge to turn multiple observables into a single observable.
merge(sourceOne, sourceTwo, sourceThree, sourceFour).subscribe((data) => updateState(data))
Here is a link to a stackblitz example: https://stackblitz.com/edit/rxjs-cg31qz?devToolsHeight=33&file=index.ts
I think what you are looking for Promise.race, it will populate the first promise has been resolved.
The Promise.race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.
const arr [obj1.product_id, obj2.product_id, obj3.product_id, obj4.product_id]
const result = Promise.race(arr.map(i => http.get(i))
The answer to your question is Promise.race() which fires, everything in then() callback function, after one of the passed promises has been resolved.
const prom1 = new Promise(r => {
setTimeout(() => {
r('prom1')
}, 1200)
})
const prom2 = new Promise(r => {
setTimeout(() => {
r('prom2')
}, 600)
})
const prom3 = new Promise(r => {
setTimeout(() => {
r('prom3')
}, 1000)
})
const prom4 = new Promise(r => {
setTimeout(() => {
r('prom4')
}, 700)
})
Promise.race([prom1, prom2, prom3, prom4]).then(r => {
console.log(r)
})
If you have RxJS installed, observables can do that.
All at once
forkJoin([sourceOne, sourceTwo, sourceThree, sourceFour])
.subscribe(([one, two, three, four]) => {});
Update client when one of them resolves :
merge(sourceOne, sourceTwo, sourceThree, sourceFour)
.subscribe(() => updateUi());
I am working with a broken codebase so I am trying to touch as little as possible. In particular, right now it's using TypeScript and ES6, and I need to launch an array of promises and wait for them all to finish before I move on with the code execution, regardless of if they resolve or reject. So this is the usecase for Promise.allSettled, which is only available in ES2020.
I tried the following implementation:
const myPromiseAllSettled = (promises) => new Promise((resolve, reject) => {
const results = []
const settle = (result) => {
results.push(result)
if (results.length === promises.length) {
(results.every(value => value) ? resolve : reject)(promises)
}
}
promises.forEach(promise => {
promise
.then(() => settle(true))
.catch(() => settle(false))
})
})
but I have only seen my own code when it comes to promises, so I would like to get some feedback on my implementation. Does it do what other developers would expect it to do? Especially when it comes to the arguments passed to resolve/reject; right now I only pass the array of promises and expect the developer to run a .then(promises => promises.forEach(...)) if they are interested in following up on the individual promises.
I also don't know ideally how I would handle the types with TypeScript here, since I am not so experienced with TypeScript as well. (Writing 'any' everywhere doesn't seem cool to me.)
it should look more like this:
const myPromiseAllSettled = (promises) => {
const fulfilled = value => ({ status: "fulfilled", value });
const rejected = reason => ({ status: "rejected", reason });
return Promise.all([...promises].map(p => Promise.resolve(p).then(fulfilled, rejected)));
}
[...promises] to handle cases where promises is iterable but not an array.
Promise.resolve(p) because the passed value may be not a Promise (or thenable).
If you simply want to be notified about the success, you can do something simpler:
const success = await Promise.all(promises).then(() => true, () => false);
Edit: Didn't like the way handled promises may be an iterable but no array. Adding a version that Array#maps over iterables:
function* map(iterable, callback) {
for (const value of iterable) {
yield callback(value);
}
}
const myPromiseAllSettled = (promises) => {
const fulfilled = value => ({ status: "fulfilled", value });
const rejected = reason => ({ status: "rejected", reason });
return Promise.all(
map(
promises,
p => Promise.resolve(p).then(fulfilled, rejected)
)
);
}
I have an array of objects and for each I execute a promise. Then, if all resolve, I expect to get a .then call in Promise.all. Problem is that with the following example I only get the output
All done in approach 2
I think the following code is pretty much explanatory but, in a nutshell, what I'm doing is:
Declare 3 objects as vals and I want to create a promise based on each.
Approach 1: use vals.map to get an array of promises based on vals
Approach 2: iterate through vals using a for and push a new promise in each iteration.
What's wrong with this code and why is it working only for approach 2?
#!/usr/bin/env node
let vals = [
{
name: "name1"
},
{
name: "name2"
},
{
name: "name3"
}
];
function newPromise(obj) {
return new Promise((resolve) => Promise.resolve(obj.name))
}
/**
* Approach 1: use .map function to get an array of promises
*/
let promises = vals.map((item) => newPromise(item));
Promise.all(promises)
.then(() => console.log("All done in approach 1"))
.catch(() => console.log("Error in approach 1"));
/**
* Approach 2: iterate through vals using a for an push a new promise for the promises array
*/
let promises2 = [];
for (let i = 0 ; i < vals.length; i++) {
promises.push(newPromise(vals[i]));
}
Promise.all(promises2)
.then(() => console.log("All done in approach 2"))
.catch(() => console.log("Error in approach 2"));
I'm using node 8.9.1 with ES6 (native Promises).
You have two slight errors which are gumming up the works and confusing things.
First off, your second approach shouldn't work because you accidentally had it pushing them to promises for the second approach instead of promises2, so you're doing Promise.all() with an empty array, which is why it worked.
Then, the reason both don't work because of how you're creating the promises. You were nesting Promise.resolve() inside new Promise(), which doesn't work. You could use either:
return new Promise(resolve => resolve(obj.name));
or
return Promise.resolve(obj.name);
Those are equivalent. Having them inside, you never called the proper resolve() function in new Promise(), so nothing ever actually resolved.
let vals = [
{
name: "name1"
},
{
name: "name2"
},
{
name: "name3"
}
];
function newPromise(obj) {
return new Promise((resolve) => resolve(obj.name))
}
/**
* Approach 1: use .map function to get an array of promises
*/
let promises = vals.map((item) => newPromise(item));
Promise.all(promises)
.then(() => console.log("All done in approach 1"))
.catch(() => console.log("Error in approach 1"));
/**
* Approach 2: iterate through vals using a for an push a new promise for the promises array
*/
let promises2 = [];
for (let i = 0 ; i < vals.length; i++) {
promises2.push(newPromise(vals[i]));
}
Promise.all(promises2)
.then(() => console.log("All done in approach 2"))
.catch(() => console.log("Error in approach 2"));
You have a problem with your newPromise function. Try to remove Promise..
For the second approach, you also have to push on the right array : promises2
let vals = [
{
name: "name1"
},
{
name: "name2"
},
{
name: "name3"
}
];
function newPromise(obj) {
return new Promise((resolve) => resolve(obj.name))
}
/**
* Approach 1: use .map function to get an array of promises
*/
let promises = vals.map((item) => newPromise(item));
Promise.all(promises)
.then(() => console.log("All done in approach 1"))
.catch(() => console.log("Error in approach 1"));
/**
* Approach 2: iterate through vals using a for an push a new promise for the promises array
*/
let promises2 = [];
for (let i = 0 ; i < vals.length; i++) {
promises2.push(newPromise(vals[i]));
}
Promise.all(promises2)
.then(() => console.log("All done in approach 2"))
.catch(() => console.log("Error in approach 2"));
How do i pass additional arguments to next "step" of promise?
new Promise((resolve, reject) => {
const a = // do stuff and return a string
return Promise.all([
// execute another promise,
// execute yet another promise
])
})
.then(([resultFromPromise_1, resultFromPromise_2]) => {
// how do i pass `const a` here?
})
I can add something like new Promise(resolve => resolve(a)) into Promise.all array, but this looks ugly. Is there better way to pass data in such cases?
I can add something like new Promise(resolve => resolve(a)) into Promise.all array, but this looks ugly. Is there better way to pass data in such cases?
Yes: Use then. If you already have a promise, using new Promise is never needed. then creates a promise, which waits for the resolution of the one you called it on, and then gets resolved with what you return from the then callback or gets rejected if you throw an exception. One of the keys of promises is how using then (and catch) transforms things at each link in the chain.
In that specific case, you'd use then on the original promise and use its callback to transform the result using a (although if you want to wait until they're all done, you can do that too; covered later).
Side note: The new Promise line at the beginning of the code of your question shouldn't be there, you don't return a promise out of the promise executor (the callback you pass to new Promise).
Example:
const a = "some string";
Promise.all([
getPromise("one").then(result => result + " - " + a), // ***
getPromise("two")
])
.then(results => {
console.log(results);
});
function getPromise(str) {
// (Could use Promise.resolve here; emphasizing asynchronousness)
return new Promise(resolve => {
setTimeout(() => {
resolve(str);
}, 250);
});
}
Alternately, if you really only want to use a when all of the promises you're passing to Promise.all have resolved, you can do that, too:
const a = "some string";
Promise.all([
getPromise("one"),
getPromise("two")
])
.then(([result1, result2]) => {
return [result1 + " - " + a, result2]; // ***
})
.then(results => {
console.log(results);
});
function getPromise(str) {
// (Could use Promise.resolve here; emphasizing asynchronousness)
return new Promise(resolve => {
setTimeout(() => {
resolve(str);
}, 250);
});
}
First off, your first promise has an error, you're not resolving it. You should do something like this:
new Promise((resolve, reject) => {
const a = 1;
resolve(Promise.all([
...
]))
})
And as for your question, instead of new Promise(resolve => resolve(a)) you can just pass a directly to the all array. ie:
new Promise((resolve, reject) => {
const a = 1;
resolve(Promise.all([
Promise.resolve("a"),
Promise.resolve("b"),
a,
]))
})
.then(([resultFromPromise_1, resultFromPromise_2, a]) => {
console.log(a);
})
I'm learning RxJS and Angular 2. Let's say I have a promise chain with multiple async function calls which depend on the previous one's result which looks like:
var promiseChain = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
}).then((result) => {
console.log(result);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result + 2);
}, 1000);
});
}).then((result) => {
console.log(result);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result + 3);
}, 1000);
});
});
promiseChain.then((finalResult) => {
console.log(finalResult);
});
My attempts at doing the same solely using RxJS without the use of promises produced the following:
var observableChain = Observable.create((observer) => {
setTimeout(() => {
observer.next(1);
observer.complete();
}, 1000);
}).flatMap((result) => {
console.log(result);
return Observable.create((observer) => {
setTimeout(() => {
observer.next(result + 2);
observer.complete()
}, 1000);
});
}).flatMap((result) => {
console.log(result);
return Observable.create((observer) => {
setTimeout(() => {
observer.next(result + 3);
observer.complete()
}, 1000);
});
});
observableChain.subscribe((finalResult) => {
console.log(finalResult);
});
It yields the same output as the promise chain. My questions are
Am I doing this right? Are there any RxJS related improvements that I can make to the above code
How do I get this observable chain to execute repeatedly? i.e. Adding another subscription at the end just produces an additional 6 though I expect it to print 1, 3 and 6.
observableChain.subscribe((finalResult) => {
console.log(finalResult);
});
observableChain.subscribe((finalResult) => {
console.log(finalResult);
});
1
3
6
6
About promise composition vs. Rxjs, as this is a frequently asked question, you can refer to a number of previously asked questions on SO, among which :
How to do the chain sequence in rxjs
RxJS Promise Composition (passing data)
RxJS sequence equvalent to promise.then()?
Basically, flatMap is the equivalent of Promise.then.
For your second question, do you want to replay values already emitted, or do you want to process new values as they arrive? In the first case, check the publishReplay operator. In the second case, standard subscription is enough. However you might need to be aware of the cold. vs. hot dichotomy depending on your source (cf. Hot and Cold observables : are there 'hot' and 'cold' operators? for an illustrated explanation of the concept)
Example for clarification:
Top of pipe can emit n values (this answers "How do I get this observable chain to execute repeatedly"), but subsequent chained streams emit one value (hence mimicing promises).
// Emit three values into the top of this pipe
const topOfPipe = of<string>('chaining', 'some', 'observables');
// If any of the chained observables emit more than 1 value
// then don't use this unless you understand what is going to happen.
const firstObservablePipe = of(1);
const secondObservablePipe = of(2);
const thirdObservablePipe = of(3);
const fourthObservablePipe = of(4);
const addToPreviousStream = (previous) => map(current => previous + current);
const first = (one) => firstObservablePipe.pipe(addToPreviousStream(one));
const second = (two) => secondObservablePipe.pipe(addToPreviousStream(two));
const third = (three) => thirdObservablePipe.pipe(addToPreviousStream(three));
const fourth = (four) => fourthObservablePipe.pipe(addToPreviousStream(four));
topOfPipe.pipe(
mergeMap(first),
mergeMap(second),
mergeMap(third),
mergeMap(fourth),
).subscribe(console.log);
// Output: chaining1234 some1234 observables1234
You could also use concatMap or switchMap. They all have subtle differences. See rxjs docs to understand.
mergeMap:
https://www.learnrxjs.io/learn-rxjs/operators/transformation/mergemap
concatMap:
https://www.learnrxjs.io/learn-rxjs/operators/transformation/concatmap
switchMap:
https://www.learnrxjs.io/learn-rxjs/operators/transformation/switchmap