Resolving an array of promises in parallel, with error handling - javascript

I have a collection of promises or async functions, I need to manipulate slightly and then execute in parallel.
The issue I am having is that I am unable to resolve the promises.
// Some dummy tasks:
const taskA = () => Promise.resolve(500);
const taskB = () => {
return new Promise(resolve => resolve(300));
};
// Push them into an array:
const tasks = [];
const registerTask = (name, task) => {
tasks.push( async () => {
return { [name]: await task() };
});
};
// trying to get the results
const runTasks = () => {
const result = Promise.all(tasks).then(results => results);
return result;
}
// usage
registerTask('taskA', taskA);
registerTask('taskB', taskB);
console.log(runTasks())
Following the successful resolution of promises ideally I would also like to handle errors individually for each task.

The problem is that your registerTask function pushes functions onto the tasks array, not Promise objects. If you change the function like this, it should work:
const registerTask = (name, task) => {
const asyncFunc = async () => {
return { [name]: await task() };
};
tasks.push( asyncFunc() ); // This pushes a promise into 'tasks'
};
Your original code ended up with tasks being an array of functions that have not been called yet. When you call Promise.all(tasks) it ended immediately with undefined for each entry in the array because there were no Promise objects to wait on.
As for error handling, the Promise.all will fail with the first rejection or exception that happens. If you want to handle each task's individual error case, then you should do that in the Promise created in registerTask since doing it during the Promise.all is too late. It's hard to give an example since I'm not sure what kind of error handling would be appropriate for tasks, but perhaps something like this:
const registerTask = (name, task) => {
const asyncFunc = async () => {
return { [name]: await task() };
};
const promise = asyncFunc()
.catch(err => {
return handleError(name, err);
});
tasks.push( promise );
};
function handleError(name, err) {
console.log(`Task ${name} had error ${err}`);
// don't re-throw the error if you want all tasks to complete
// return some error object so you can see which tasks failed after they're done
return {error: err, name: name};
}

Promise.all expects an array of promises and you are passing an array of functions that returns a promise. So change your promise Promise.all to receive the promise of each function:
// Some dummy tasks:
const taskA = () => Promise.resolve(500);
const taskB = () => {
return new Promise(resolve => resolve(300));
};
// Push them into an array:
const tasks = [];
const registerTask = (name, task) => {
tasks.push( async () => {
return { [name]: await task() };
});
};
// trying to get the results
const runTasks = () => {
const result = Promise.all(tasks.map(task => task())); // Execute the tasks to receive the promises
return result;
}
// usage
registerTask('taskA', taskA);
registerTask('taskB', taskB);
runTasks().then((res) => {console.log(res)})

Related

Recursive function in Redux

Trying to pass a reference to the recursive function to check if Redux action data fetch is complete, but getting function reference errors
const fetchAccountComplete = (state, accountNo) => { //state here is function reference
return new Promise(resolve => {
(function waitForFetchComplete(state, accountNo) {
const {isFetching, receivedAt} = state().account[accountNo] // getting state not a function here
if (!isFetching) return resolve()
setTimeout(waitForFetchComplete, 100)
})()
})
}
Is there a better way to return a promise to the caller function in Redux dispatch actions so that once the data is fetched, i need to do some other logic in other action.
Update 1:
should have been more clearer. There are two callers of this Request, Recieve actions on say Account data. First caller is directed similar to the above comment by you so waits until complete, second caller would not be doing the async call again and need to check if data fetch is complete so trying to see if recursive function with check on state so that promise can be resolved is being done
You could take advantage of promising chaining.
Example:
Have three actions like: IS_FETCHING, FETCH_SUCCESS, FETCH_ERROR.
IS_FETCHING:
Will simply set your state as pending (may be useful for showing a loading animation, for example).
FETCH_SUCCESS:
Will contain the result of the fetch to update the state. Will also clear the isUpdating flag
FETCH_ERROR:
Will contain any possible error due to the fetch (application or network error). Will also clear the isUpdating flag
Then, what you could do at application level is:
dispatch({type: IS_FETCHING, payload: data});
fetch(`https://MY-SERVER.com/?data=${data}`)
.then(response => response.json())
.then(json =>
dispatch({
type: isError(json) ? FETCH_RESULT : FETCH_ERROR,
payload: json
})
);
You could even benefit of action creators for the job.
Here is a good guide for that: https://redux.js.org/advanced/async-actions
If you have a function that returns a promise that is called multiple times with the same arguments then you can group that in a way so that the function is not called when it still has an unresolved promise and something tries to call it again with the same arguments.
Here is an example:
//group promise returning function
const createGroup = (cache) => (
fn,
getKey = (...x) => JSON.stringify(x)
) => (...args) => {
const key = getKey(args);
let result = cache.get(key);
if (result) {
return result;
}
//no cache
result = Promise.resolve(fn.apply(null, args)).then(
(r) => {
cache.done(key); //tell cache promise is done
return r;
},
(e) => {
cache.done(key); //tell cache promise is done
return Promise.reject(e);
}
);
cache.set(key, result);
return result;
};
//creates a cache that will remove cached value when
// Promise is done (resolved or rejected)
const createCache = (cache = new Map()) => {
return {
get: (key) => cache.get(key),
set: (key, value) => cache.set(key, value),
done: (key) => cache.delete(key),
};
};
//function that retuns a promise
const later = (time, value) => {
console.log('executing later with values', time, value);
return new Promise((r) =>
setTimeout(() => r(value), time)
);
};
//create group function with a cache that will remove
// cache key when promise is resolved or rejected
const groupAndRemoveCacheOnDone = createGroup(
createCache()
);
//grouped version of the later function
const groupedLater = groupAndRemoveCacheOnDone(later);
//testing the groped later
groupedLater(100, 8); //first call causes console.log
groupedLater(100, 8); //same arguments will not call later
groupedLater(100, 8); //will not call later
//will call later because arguments are not the same
// as the other calls
groupedLater(100, 'XX');
groupedLater(100, 8) //will not call later
.then((value) => {
console.log('resolved with:', value);
//this will call later because cache value is removed
// after promise is resolved
return groupedLater(100, 8);
})
.then(() => {
//testing with fetchAccountComplete
console.log(
'***** how many times is fetchAccountComplete called *****'
);
const fetchAccountComplete = (state, accountNo) => {
console.log(
'fetchAccountComplete called with',
accountNo
);
return new Promise((resolve) => {
(function waitForFetchComplete(state, accountNo) {
const {
isFetching,
receivedAt,
} = state().account[accountNo]; // getting state not a function here
if (!isFetching) return resolve();
setTimeout(
() => waitForFetchComplete(state, accountNo),
100
);
})(state, accountNo);
});
};
const data = {
account: [{ isFetching: true }],
};
const state = () => data;
const groupedFetchAccountComplete = groupAndRemoveCacheOnDone(
fetchAccountComplete
);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0).then((resolve) =>
console.log('resolved')
);
data.account[0].isFetching = false;
});

How do I unit test the result of a 'then' of a promise in JavaScript?

I'm using TypeScript to write a very simple service that utilizes the AWS SDK. My Jest unit tests are passing, but the coverage reports are saying that the line 'return result.Items' is not covered. Can anyone tell why this is? Is it a bug in jest?
// service file
/**
* Gets an array of documents.
*/
function list(tableName) {
const params = {
TableName: tableName,
};
return docClient
.scan(params)
.promise()
.then((result) => {
return result.Items;
});
}
// test file
const stubAwsRequestWithFakeArrayReturn = () => {
return {
promise: () => {
return { then: () => ({ Items: 'fake-value' }) };
},
};
};
it(`should call docClient.scan() at least once`, () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
db.list('fake-table');
expect(mockAwsCall).toBeCalledTimes(1);
});
it(`should call docClient.scan() with the proper params`, () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
db.list('fake-table');
expect(mockAwsCall).toBeCalledWith({
TableName: 'fake-table',
});
});
it('should return result.Items out of result', async () => {
const mockAwsCall = jest
.fn()
.mockImplementation(stubAwsRequestWithFakeArrayReturn);
aws.docClient.get = mockAwsCall;
const returnValue = await db.get('fake-table', 'fake-id');
expect(returnValue).toEqual({ Items: 'fake-value' });
});
The line not covered is the success callback passed to then.
Your mock replaces then with a function that doesn't accept any parameters and just returns an object. The callback from your code is passed to the then mock during the test but it doesn't call the callback so Jest correctly reports that the callback is not covered by your tests.
Instead of trying to return a mock object that looks like a Promise, just return an actual resolved Promise from your mock:
const stubAwsRequestWithFakeArrayReturn = () => ({
promise: () => Promise.resolve({ Items: 'fake-value' })
});
...that way then will still be the actual Promise.prototype.then and your callback will be called as expected.
You should also await the returned Promise to ensure that the callback has been called before the test completes:
it(`should call docClient.scan() at least once`, async () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
await db.list('fake-table'); // await the Promise
expect(mockAwsCall).toBeCalledTimes(1);
});
it(`should call docClient.scan() with the proper params`, async () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
await db.list('fake-table'); // await the Promise
expect(mockAwsCall).toBeCalledWith({
TableName: 'fake-table',
});
});
The Library chai-as-promised is worth looking at.
https://www.chaijs.com/plugins/chai-as-promised/
Instead of manually wiring up your expectations to a promise’s
fulfilled and rejected handlers.
doSomethingAsync().then(
function (result) {
result.should.equal("foo");
done();
},
function (err) {
done(err);
}
);
you can write code that expresses what you really mean:
return doSomethingAsync().should.eventually.equal("foo");

React Native Wait All Image Prefetch Done

I'm trying to prefetch multiple image before navigating to another screen, but returnedStudents all undefined.
prepareStudentImages = async (students) => {
let returnedStudents = students.map(student => {
Image.prefetch(student.image)
.then((data) => {
...
})
.catch((data) => {
...
})
.finally(() => {
return student;
});
});
await console.log(returnedStudents); // ----> all items undefined
}
There are a couple of things to fix with this:
1) Your map() function does not return anything. This is why your console log is undefined.
2) Once your map functions work, you are logging an array of promises. To deal with multiple promises (an array), you can use Promise.all().
So I think to fix this, you can do:
prepareStudentImages = async (students) => {
const returnedStudents = students.map(student =>
Image.prefetch(student.image)
.then((data) => {
...
})
.catch((data) => {
...
})
.finally(() => {
return student
})
)
console.log(returnedStudents) // log the promise array
const result = await Promise.all(returnedStudents) // wait until all asyncs are complete
console.log(result) // log the results of each promise in an array
return result
}

Javascript implement async to get result of long-running process into array

I have an array of files that I am adding data to which conceptually works like this:
let filearray = ['file1.txt', 'file2.txt', 'file3.txt'];
newarray = [];
for (let f of filearray) {
newstuff = 'newstuff';
newarray.push([f, newstuff]);
}
console.log(newarray)
// Returns expected array of arrays
However, what I need to do is make newstuff = slow_promise_function(f); that involves lots of processing. How do I get the value from that promise function into the array?
Ideally I'd like to use the new async feature in ES2017.
Update:
These answers are helping me understand the problems (and solutions):
https://stackoverflow.com/a/37576787/1061836
https://stackoverflow.com/a/43422983/1061836
You could use Promise.all which returns a single Promise that resolves when all of the promises have resolved:
let loadData = async () => {
let filearray = ['file1.txt', 'file2.txt', 'file3.txt'];
try {
let ops = filearray.map(f => slow_promise_function(f));
let newarray = await Promise.all(ops);
// TODO: use newarray
} catch (err) {
console.log(err);
}
}
loadData();
async/await is a nice way to accomplish this, as you suspected:
console.log('Starting...');
let files = ['file1.txt', 'file2.txt', 'file3.txt'];
Promise.all(files.map(async f => [f, await slow_promise_function(f)]))
.then(files => console.log('got results: ', files));
function slow_promise_function(file) {
return new Promise(res => setTimeout(_ => res(`processed ${file}`), 500));
}
Well that can simply be done with Promises more info on this link Promises.
const newStuffFunc = async () => {
try {
let newStuff = await slow_promise_function(f);
// new stuff is a promise. so you either do it with await again or with then/catch
let data = await newStuff;
} catch (e){
}
}
const slow_promise_function = (url) => {
return new Promise((resolve, reject) => {
// do something asynchronous which eventually calls either:
//
// resolve(someValue); // fulfilled
// or
// reject("failure reason"); // rejected
});
};
This link can show you more usability of async/promises into javascript.

Promise then and catch clauses not working

Background
I have a multitude of functions that may or may not return a Promise and that may or may not fail, like in the examples below:
let goodFun = (fruit) => `I like ${fruit}`;
let badFun = (fruit) => {
throw `${fruit} is spoiled!`;
};
let badPromise = (fruit) => new Promise( (fulfil, reject) => {
reject(`Promise failed with ${fruit}`);
});
let goodPromise = (fruit) => new Promise((fulfil, reject) => {
fulfil(`Promise succeeded with ${fruit}`);
});
Because each function may or may not return a promise, which may or may not fail, I modified executeSafe from another StackOverflow post, that takes a function and its arguments as parameters, and then Promisifies the function and its result:
let executeSafe =
(fun, ...args) => Promise.resolve().then(() => {
return fun(...args);
});
My objective with all of this, is to have an asyncFun function that waits for the execution of a batch of functions that were Promisified and then returns whatever came from executing them:
let asyncFun =
(fruit) =>
Promise.all([badFun, goodFun, badPromise, goodPromise].map(
fun => executeSafe(fun, fruit)
)
);
Problem
asyncFun is designed to run the multitude of functions previously described and some of them I actually expect to see fail. To accommodate for this my asyncFun function has a catch clause. This clause only works with badFun and doesn't work with badPromise. The then clause never works.
let executor = () => {
let fruitsArr = ["banana", "orange", "apple"];
let results = [];
for (let fruit of fruitsArr)
results.push(
asyncFun(fruit)
.then(res => {
console.log(res);
})
.catch(error => {
console.log(`Error: ${error}`);
})
);
return Promise.all(results);
};
Not even placing the catch clause in the executor's call catches anything else other than the badFun errors.
executor()
.catch(error => console.log("Failed miserably to catch error!"));
Code
let goodFun = (fruit) => {
return `I like ${fruit}`;
};
let badFun = (fruit) => {
throw `${fruit} is spoiled!`;
};
let badPromise = (fruit) => Promise.resolve().then(() => {
throw `Promise failed with ${fruit}`;
});
let goodPromise = (fruit) => Promise.resolve().then(() => {
return `Promise succeded with ${fruit}`;
});
let executeSafe =
(fun, ...args) => Promise.resolve().then(() => {
return fun(...args);
});
let asyncFun = (fruit) => Promise.all([badFun, goodFun, badPromise, goodPromise].map(fun => executeSafe(fun, fruit)));
let executor = () => {
let fruitsArr = ["banana", "orange", "apple"];
let results = [];
for (let fruit of fruitsArr)
results.push(
asyncFun(fruit)
.then(res => {
console.log(res);
})
.catch(error => {
console.log(`Error: ${error}`);
})
);
return Promise.all(results);
};
executor()
.catch(error => console.log("Failed miserably to catch error!"));
Question:
How to fix my code so that its then and catch clauses in asyncFun work as intended?
The problem here is that you are misunderstanding how Promise.all() works. The way it works is that if all of the promises succeed, it resolves to an array of values. If any of them fail, it rejects with the first error that occurred.
What you seem to be expecting, that the successful ones are caught in the then and the failed ones are caught in the catch, is not possible. A promise either resolves once or it rejects once. It doesn't do both, and it won't do one or the other multiple times. Promise.all() returns a single promise so it will either resolve or reject.
Third party promise libraries do have methods for "settling" an array of promises - basically waiting until they have all done their thing (succeeded or failed), and resolving to an array of the results. You can implement it like this:
// values is an array of promises and/or non-promise values
function allSettled(values) {
let settle =
value => Promise.resolve(value)
.then(result => ({ state: "fulfilled", value: result }))
.catch(error => ({ state: "rejected", reason: error }));
return Promise.all(values.map(settle));
}
// example usage
allSettled(['hello', 'goodbye', Promise.resolve('good'), Promise.reject('bad')])
.then(results => console.log(results));
Then you can use it like below.
On an unrelated note, I've also modified your approach so that you don't need the modified version of executeSave that takes ...args (I think that's a convoluted way to go about it). You can create functions that use the arguments before you pass them into _executeSafe_:
let goodFun = (fruit) => `I like ${fruit}`;
let badFun = (fruit) => {
throw `${fruit} is spoiled!`;
};
let badPromise = (fruit) => Promise.reject(`Promise failed with ${fruit}`);
let goodPromise = (fruit) => Promise.resolve(`Promise succeeded with ${fruit}`);
let executeSafe = fun => Promise.resolve().then(fun);
function allSettled(values) {
let settle =
value => Promise.resolve(value)
.then(result => ({ state: "fulfilled", value: result }))
.catch(error => ({ state: "rejected", reason: error }));
return Promise.all(values.map(settle));
}
let asyncFun =
(fruit) =>
allSettled([badFun, goodFun, badPromise, goodPromise]
.map(fun => () => fun(fruit))
.map(executeSafe)
);
asyncFun("orange").then(results => console.log(results));
Additional side note - if you wanted to use the promisify function from Jared Smith's answer, then you could change your asyncFun function to this:
let asyncFun =
(fruit) =>
allSettled([badFun, goodFun, badPromise, goodPromise]
.map(promisify)
.map(fun => fun(fruit))
);
So this works, catches fine. If the function passed to promisify returns a promise, it just gets passed up the chain.
var fnThatThrows = s => { throw new Error(s) };
var identity = x => x;
var promisify = f => (...args) => new Promise((res, rej) => {
try {
res(f(...args));
} catch (e) {
rej(e);
}
});
var rejected = promisify(fnThatThrows);
rejected("foo").then(_ => console.log("shouldn't see")).catch(e => console.log(e)); // logs error
var succeded = promisify(identity);
succeeded("foo").then(val => console.log(val)); // logs 'foo'
If the promise from the argument function is rejected, that gets passed along too:
var returnsBadPromise = s => Promise.reject(new Error(s));
promisify(returnsBadPromise)("foo").catch(e => console.log(e)); // logs error

Categories