The idea is to sequentially iterate the array but parallel process each item in the subarray.
Once record #1 is processed in parallel then it moves to the record #2 and parallel process it's items and so on. So basically it's a combination of sequentiality and parallelism.
Concat all results in a single dimension array and display. (pending)
If input contains an array of arrays.
var items = [
["item1", "item2"],
["item3", "item4"],
["item5", "item6"],
["item7", "item8"],
["item9", "item10"]
]
And an action that processes these items.
function action(item) {
return new Promise(function(resolve, reject){
setTimeout(function(){
resolve(item + ":processed");
}, 100)
});
}
Attempt
describe("", function(){
this.timeout(0);
it("should", function(done){
items.reduce(function(accumulator, currentValue, currentIndex, array){
return accumulator.then(function(result){
return new Promise(function(resolve, reject){
Promise.all(currentValue.map(action))
.then(resolve, reject);
});
});
}, Promise.resolve())
});
});
Expectations:
Ideally a clean minimalistic and a functional approach (no state) to return the results to the caller.
Attempt 2
var chain = items.reduce(function(accumulator, currentValue, currentIndex, array){
return accumulator.then(function(result){
return new Promise(function(resolve, reject){
Promise.all(currentValue.map(action))
.then(resolve, reject);
});
});
}, Promise.resolve());
chain.then(console.log, console.error); // I need all results here
displays last result only. [ 'item9:processed', 'item10:processed' ]
Edit final solution based on the answer.
var chain = items.reduce(function(accumulator, currentValue, currentIndex, array){
return accumulator.then(function(result){
return new Promise(function(resolve, reject){
Promise.all(currentValue.map(action))
.then(function(data){
resolve(result.concat(data)) // new array
}, reject);
});
});
}, Promise.resolve([]));
chain.then(console.log, console.error);
One way to do this:
var items = [
["item1", "item2"],
["item3", "item4"],
["item5", "item6"],
["item7", "item8"],
["item9", "item10"]
]
function action(item) {
return new Promise(function(resolve, reject){
setTimeout(function(){
resolve(item + ":processed");
}, 100)
});
}
function process(items) {
return items.reduce((m, d) => {
const promises = d.map(i => action(i));
let oldData;
return m.then((data) => {
oldData = data;
return Promise.all(promises);
})
.then(values => {
//oldData.push(...values);
oldData.push.apply(oldData, values);
return Promise.resolve(oldData);
})
}, Promise.resolve([]))
}
process(items).then(d => console.log(d))
//Prints:
// ["item1:processed","item2:processed","item3:processed","item4:processed","item5:processed","item6:processed","item7:processed","item8:processed","item9:processed","item10:processed"]
A simple functional way of doing this would be like
map sub array with parallel data by promise returning async functions
Promise.all() the resulting promises array
sequence the sub arrays at the .then() stage of Promise.all() in a recursive fashion
ES6; tools like spread syntax / rest parameters and array destructuring are very handy for this job.
var items = [["item1", "item2"],
["item3", "item4"],
["item5", "item6"],
["item7", "item8"],
["item9", "item10"]],
act = i => new Promise(v => setTimeout(v, 1000, `${i}: processed`)),
seqps = ([is,...iss]) => is && Promise.all(is.map(i => act(i)))
.then(([p,q]) => (console.log(`${p} and ${q}`),
seqps(iss)));
seqps(items);
Related
EDIT
So I am creating an e-commerce website and I'm running into a few road blocks. I have since updated my code to make it cleaner and easier to read and I somewhat pinpointed where my issue stems from (though there could be more). Here is the updated code:
StoreDB.prototype.addOrder = function(order) {
console.log('addOrder');
return this.connected.then(function(db) {
console.log(order);
var orders = db.collection('orders');
var products = db.collection('products');
return insert(order, orders, db)
.then(function(updatedOrdersList) {
return obtainOrders(updatedOrdersList);
})
.then(function(orderList) {
return updateProducts(orderList, products);
})
.catch((err) => console.log('There was an issue in the promise chain\n' + err));
});
}
function insert(order, orders, db) {
return new Promise(function(resolve, reject) {
console.log('Inserting order into orders collection');
orders.insert(order);
resolve(db.collection('orders'));
});
}
function obtainOrders(orders) {
return new Promise(function(resolve, reject) {
console.log('Obtaining orders');
var orderList = orders.find({}).toArray();
resolve(orderList);
});
}
function updateProducts(orderList, products) {
return new Promise(function(resolve, reject) {
console.log('Getting the products that match the cart')
var promises = [];
var promises2 = [];
console.log('orderList', orderList);
for (var item in orderList[0].cart) {
console.log('item', item);
promises.push(products.find({$and : [{"_id" : item}, {"quantity" : {$gte: 0}}]}).toArray());
}
Promise.all(promises)
.then(() => {
console.log('Updating the cart quantity')
for (var i = 0; i < promises.length; i++) {
console.log(promises);
// console.log(productList[item], productList[item]._id, productList[item].quantity, cart);
promises2.push(products.update({"_id" : productList[item]._id}, {$set :{"quantity" : (productList[item].quantity - cart[productList[item]._id])}}));
promises2.push(products.find({}).toArray());
Promise.all(promises2)
.then(() => promises[promises2.length-1]);
}
});
});
}
It appears that when obtainOrders is called in the promise chain, it returns an empty array. I'm not too sure why it does this because in the chain where:
return insert(order, orders, db)
.then(function(updatedOrdersList) {
return obtainOrders(updatedOrdersList);
})
.then(function(orderList) {
return updateProducts(orderList, products);
})
The function call to obtainOrders from above waits for the method call to Collections.find to be completed before it is resolved (returning an array of orders). Some reason it is returning an empty array.
Any help would be greatly appreciated!
Not sure what the relationship between the first block and second is, but in the first block:
function insert(order, orders, db) {
return new Promise(function(resolve, reject) {
console.log('Inserting order into orders collection');
orders.insert(order);
resolve(db.collection('orders'));
});
}
At a glance it seems like orders.insert(order) is asynchronous, but you're not waiting for it to finish before resolving.
You could either make the function asynchronous and
await orders.insert(order);
or
orders.insert(order)
.then(() => resolve(db.collection('orders')))
But doesn't having the method Collections.insert inside a promise make that method call asynchronous?
Yes, Collections.insert is implicitly asynchronous in that it returns a Promise. I was talking about declaring it async explicitly so you can await other async calls in the function body. Note the addition of async at the start and await orders.insert().
async function insert(order, orders, db) {
return new Promise(function(resolve, reject) {
console.log('Inserting order into orders collection');
await orders.insert(order);
resolve(db.collection('orders'));
});
}
If you don't await orders.insert (either via await or .then) you're calling insert and immediately returning the db.collection call, which creates a race condition where the new order may not be inserted yet. It's possible the new order could be there but it seems unlikely, and it's not going to be there reliably.
There's also no need to create a new Promise here. db.collection already returns a Promise. Just return the one you already have:
function insert(order, orders, db) {
console.log('Inserting order into orders collection');
return orders.insert(order)
.then(() => db.collection('orders'));
}
Take a look at this sample code snippet. I've mocked out the orders.insert and db.collection methods.
// mock orders.insert
const orders = {
insert: async() => {
return new Promise((resolve) => {
// wait a second then resolve
setTimeout(resolve, 1000);
});
}
}
// mock db.collection
const db = {
collection: async() => {
return new Promise((resolve) => {
// wait a second then resolve with mock data
setTimeout(() => resolve(['foo', 'bar', 'baz']), 1000);
});
}
}
function insert(order, orders, db) {
return orders.insert(order)
.then(() => db.collection('orders'));
}
// same as above, but with async/await
async function insertAsync(order, orders, db) {
await orders.insert(order);
return await db.collection('orders');
}
function go() {
// using then
insert(null, orders, db).then(console.log);
// using async/await;
insertAsync(null, orders, db).then(console.log);
}
go();
I try to parse all the files that were uploaded as a directory upload into a single array of files. I have a problem that I cannot combine result of all the promises into a single array. I get a multidimensional array instead.
function iterateThroughUploadedFiles(files, isEntry = false) {
var promises = [];
for (let i = 0; i < files.length; i++) {
let entry = isEntry ? files[i].webkitGetAsEntry() : files[i];
if (entry.isFile) {
promises.push(new Promise(function(resolve, reject) {
getFile(entry, resolve, reject);
}));
}
else if (entry.isDirectory) {
let dirReader = entry.createReader();
var promise = new Promise(function(resolve, reject) {
readEntries(dirReader, resolve, reject);
});
promises = promises.concat(promise);
}
}
return Promise.all(promises);
}
function getFile(fileEntry, resolve, reject) {
fileEntry.file(function(file) {
resolve(file)
});
}
function readEntries(dirReader, resolve, reject) {
dirReader.readEntries(function (entries) {
resolve(iterateThroughEntries(entries).then(function(result) {
return result;
}));
});
}
Usage:
iterateThroughUploadedFiles(files, true).then(function(result) {
console.log(result);
});
When I execute iterateThroughUploadedFiles function the result variable is a multidimensional array. However, I want this array to be flattened (example: [File(6148), Array(1), Array(0), File(14)]). I am not very familiar with callbacks and Promises...therefore, I have some issues working with them.
Edit:
There is a typo I made inside readEntries(dirReader, resolve, reject) function. There should be iterateThroughUploadedFiles instead of iterateThroughEntries function.
return Promise.all(promises);
That resolves to a two dimensional array, which you can easily flatten by using some generator functions:
function* flatten(arr) {
for(const el of arr) {
if(Array.isArray(el)) {
yield* flatten(el);
} else {
yield el;
}
}
}
return Promise.all(promises).then(array => ([...flatten(array)]));
While passing resolve and reject works kind of, that can be done way more elegant with just returning a new Promise from the getFile function:
function getFile(fileEntry, resolve, reject) {
return new Promise(resolve => fileEntry.file(resolve));
}
Then it can be easily chained
function readEntries(dirReader) {
return new Promise(resolve => dirReader.readEntries(resolve))
.then(iterateThroughEntries);
}
function iterateThroughUploadedFiles(files, isEntry = false) {
return Promise.all( files.map( file => {
let entry = isEntry ? file.webkitGetAsEntry() : file;
if (entry.isFile) {
return getFile(entry);
} else if (entry.isDirectory) {
return readEntries( entry.createReader());
}
})).then(array => ([...flatten(array)]));
}
I have a dynamic number of promises that I need to run sequentially.
I understood how I can run sequentially promises but I don't succeed to make it dynamic with a number of promises that could vary.
Here is a way I found to do it statically How to resolve promises one after another? :
function waitFor(timeout) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(`Finished waiting ${timeout} milliseconds`);
}, timeout);
});
}
waitFor(1000).then(function(result) {
$('#result').append(result+' # '+(new Date().getSeconds())+'<br>');
return waitFor(2000);
}).then(function(result) {
$('#result').append(result+' # '+(new Date().getSeconds())+'<br>');
return waitFor(3000);
}).then(function(result) {
$('#result').append(result+' # '+(new Date().getSeconds())+'<br>');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div id="result"></div>
I would like to do the same but instead of 3 nested promises, I would like to have any number I want.
Can you help me ?
Thanks a lot!!
There are three basic ways to achieve this task with Promises.
.reduce() pattern.
function waitFor(timeout) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(`Finished waiting ${timeout} milliseconds`);
}, timeout);
});
}
var timeouts = [1000, 2000, 2000, 3000, 1000],
sequence = tos => tos.reduce((p,c) => p.then(rp => waitFor(c))
.then(rc => console.log(`${rc} # ${new Date().getSeconds()}`)), Promise.resolve());
sequence(timeouts);
The recursive pattern.
function waitFor(timeout) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(`Finished waiting ${timeout} milliseconds`);
}, timeout);
});
}
var timeouts = [1000, 2000, 2000, 3000, 1000],
sequence = ([to,...tos]) => to !== void 0 && waitFor(to).then(v => (console.log(`${v} # ${new Date().getSeconds()}`), sequence(tos)));
sequence(timeouts);
Scan from left pattern.
The scanl pattern would sequence promises one after another but once it is completed you also have access to the interim promise resolutions. This might be useful in some cases. If you are going to construct an asynchronous tree structure lazily (branching from the nodes only when needed) you need to have access to the previous promise resolutions.
In order to achieve scanl functionality in JS, first we have to implement it.
var scanl = (xs, f, acc) => xs.map((a => e => a = f(a,e))(acc))
we feed scanl with xs which is the array of timeouts in this particular example, f which is a callback function that takes acc (the accumulator) and e (current item) and returns the new accumulator. Accumulator values (the interim promise resolutions) are mapped over the timeouts array to be accessed when needed.
function waitFor(timeout) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(`finished waiting ${timeout} milliseconds`);
}, timeout);
});
}
var timeouts = [1000, 2000, 2000, 3000, 1000],
scanl = (xs, f, acc) => xs.map((a => e => a = f(a,e))(acc)),
proms = scanl(timeouts, // input array
(a,t,r) => a.then(v => (r = v, waitFor(t))) // callback function
.then(v => (console.log(`${r} and ${v}`),
`${r} and ${v}`)),
Promise.resolve(`Started with 0`)); // accumulator initial value
// Accessing the previous sub sequential resolutions
Promise.all(proms)
.then(vs => vs.forEach(v => console.log(v)));
.as-console-wrapper {
max-height: 100% !important
}
Make a seprate function to handle the number of iterations
function waitFor(timeout) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(`Finished waiting ${timeout} milliseconds`);
}, timeout);
});
}
function resultHandler(result) {
$('#result').append(result+' # '+(new Date().getSeconds())+'<br>');
return waitFor(2000);
}
function repeat(promise,num){
if(num>0)
repeat(promise.then(resultHandler),num-1);
}
repeat(waitFor(1000),2)
Forget I commented (when you convert a Promise to Observable or include the promise in an array, the Promise is executed). You can use a "recursive" function
foolPromise(index: number, interval: number) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id: index, data: new Date().getTime() % 100000 });
}, interval);
})
}
intervals: number[] = [1000, 50, 500];
recursive(req: any, index: number) {
req.then(res => {
console.log(res);
index++;
if (index < this.intervals.length)
this.recursive(this.foolPromise(index, this.intervals[index]), index);
})
}
ngOnInit() {
this.recursive(this.foolPromise(0, this.intervals[0]), 0)
}
If you don't care for serialization, you can use Promise.all https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Promise.all([promise1, promise2, promise3]).then(function(values) {
// do something with values
}).catch(function(err) {
// error called on first failed promise
});
Or, you can use an async function:
async function doSomething(arrayOfPromises) {
for (const item of arrayOfPromises) {
const result = await item;
// now do something with result
}
};
I want to create promises chain and then dynamically add as many promises to it as it's needed. These additions could be in some cycle with dynamic number of steps so that I can't use chain like .then().then().then... Code bellow works improperly but you'll get the idea. Result should be a console logged 3000, 4000, 5000 numbers in 3, 4 and 5 seconds consequently but actually doesn't work that way. Any ideas?
let launchChain = function(delay)
{
return new Promise((resolve: Function, reject: Function) => {
setTimeout(() => {
console.log(delay);
resolve();
}, delay)
})
}
let chain = launchChain(3000);
chain.then(function () {
return launchChain(4000);
})
chain.then(function () {
return launchChain(5000);
})
So used reduce and this site
var delays = [0, 1000, 2000, 3000, 4000];
function workMyCollection(arr) {
return arr.reduce(function(promise, item) {
return promise.then(function() {
return launchChain(item);
});
// uses this orignal promise to start the chaining.
}, Promise.resolve());
}
function launchChain(delay) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log(delay);
resolve();
}, delay);
});
}
workMyCollection(delays);
[EDIT] : Another way
var delays = [0, 1000, 2000, 3000, 4000];
var currentPromise = Promise.resolve();
for (let i = 0; i < delays.length; i++) {
// let makes i block scope .. var would not have done that
currentPromise = currentPromise.then(function() {
return launchChain(delays[i]);
});
}
function launchChain(delay) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log(delay);
resolve();
}, delay);
});
}
Do let me know if this worked for you :)
thanks to this question I learned a lot!
function run(delay){
let chain = launchChain(delay);
chain.then(function() {
run(delay+1000);
});
}
run(3000);
Thanks sinhavartika! It works! But I actually took example from here
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
and changed it a bit and now I use it in my project in the following way:
/**
* Runs promises from promise array in chained manner
*
* #param {array} arr - promise arr
* #return {Object} promise object
*/
function runPromiseInSequense(arr) {
return arr.reduce((promiseChain, currentPromise) => {
return promiseChain.then((chainedResult) => {
return currentPromise(chainedResult)
.then((res) => res)
})
}, Promise.resolve());
}
var promiseArr = [];
function addToChain(delay)
{
promiseArr.push(function (delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(delay);
resolve();
}, delay)
});
}.bind(this, delay))
}
addToChain(1000);
addToChain(2000);
addToChain(3000);
addToChain(4000);
runPromiseInSequense(promiseArr);
I wonder if there is a way that I can execute an array of promises waterfally? i.e. I want the next promise not to start until the current promise is resolved.
My test:
import Promise from 'bluebird'
describe('promises waterfall', () => {
const result = []
function doItLater(val, time = 50) => {
return new Promise(resolve => {
setTimeout(() => {
result.push(val)
resolve(val)
}, time)
})
}
it('execute promises one after one resolved', done => {
result.push(1)
const promiseList = [doItLater('a', 100),doItLater('b', 1),doItLater('c')]
result.push(2)
Promise.each(
promiseList,
(output) => {
result.push(output + '-outputted')
}
)
.then(
() => {
result.push(3)
console.log(result)
expect(result).to.eql([ 1, 2, 'a', 'b', 'c', 'a-outputted', 'b-outputted', 'c-outputted', 3 ])
done()
}
)
})
})
Update
Sorry if I have made it too confusing. I'm asking how I can make my test pass. Currently my test fails - the actual result is in wrong order:
[ 1,
2,
'b',
'c',
'a',
'a-outputted',
'b-outputted',
'c-outputted',
3 ]
When you create promises like this
[doItLater('a', 100),doItLater('b', 1),doItLater('c')]
they are already executing asynchronously. There is no way of controlling their order of execution.
You can use Promise.reduce for your case, like this
Promise.reduce([['a', 100], ['b', 1], ['c']], function(res, args) {
return doItLater.apply(null, args).then(function(output) {
res.push(output + '-outputted');
return res;
});
}, result)
.then(function(accumulatedResult) {
....
});
Now, we are creating promises one by one, after the previous promise is resolved and we are accumulating the result in res (which is actually result). When all the promises are resolved, the accumulatedResult will have all the values.
Note: It is strongly recommended NOT to share data between functions. Please make sure that you have compelling reasons to do so, if you have to do.
// Promisse waterfall pattern
var promises = [1,2,3].map((guid)=>{
return (param)=> {
console.log("param", param);
var id = guid;
return new Promise(resolve => {
// resolve in a random amount of time
setTimeout(function () {
resolve(id);
}, (Math.random() * 1.5 | 0) * 1000);
});
}
}).reduce(function (acc, curr, index) {
return acc.then(function (res) {
return curr(res[index-1]).then(function (result) {
console.log("result", result);
res.push(result);
return res;
});
});
}, Promise.resolve([]));
promises.then(console.log);