-
Notifications
You must be signed in to change notification settings - Fork 102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to Aggregate Errors on Tasks? #222
Comments
Sadly there's no simple way of doing this, but you can convert the Task<A, B> to a Task<void, Validation<A, B>> — where the task always "succeeds". Then you'll need a function that will aggregate the errors from Validation. This is easier since there's no concurrency involved, and Validation already has an applicative instance that does aggregation. You'll also need control.async to run stuff in parallel. For example:
If you write a |
Sorry, I didn't understand well your example.
Where should I use the toTaskOfResults fn?
I'm also open to a solution using folktale 2 task version though.
In your example at the end there is the standard fork call, so I can't see
how you get the failures plus the successes.
…On Thu, Jul 18, 2019, 1:25 PM Quil ***@***.***> wrote:
Sadly there's no simple way of doing this, but you can convert the Task<A,
B> to a Task<void, Validation<A, B>> — where the task always "succeeds".
Then you'll need a function that will aggregate the errors from Validation.
This is easier since there's no concurrency involved, and Validation
already has an applicative instance that does aggregation.
You'll also need control.async to run stuff in parallel.
For example:
const Task = require('data.task');
const Validation = require('data.either');
const { parallel } = require('control.async');
const toTaskOfResults = task =>
task.fold(error => Task.of(Validation.Failure([error])),
value => Task.of(Validation.Success(value)));
parallel([
getDataFromAPI(userId),
getLogs(userId),
getCustomer(userId)
]).map(([a, b, c]) => liftA3(mergeResults, a, b, c))
.fork(
...
);
If you write a liftA that takes an array of things, and make it curried,
you can simplify that to .map(liftA(mergeResults)) too.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#222?email_source=notifications&email_token=ADANIASCGBXE42UMOAOLVUDQACRQTA5CNFSM4IE4YQK2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2JGLGQ#issuecomment-512910746>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ADANIARNJFQRBXN4ZNXTTDTQACRQTANCNFSM4IE4YQKQ>
.
|
Oh, oops, this is what I get for not testing things. There's so much wrong with that example, sorry (I haven't touched v1 for a long time) :') Anyway. My suggestion was that you transform all of your You can construct such operation by combining Validations and control.async's parallel (or Folktale 2's const Task = require('data.task');
const Validation = require('data.validation');
const { parallel } = require('control.async')(Task);
const toTaskOfValidations = (task) =>
task.map(Validation.of)
.orElse(error => Task.of(Validation.Failure([error])));
const liftA = (Applicative, f) => (applicatives) =>
applicatives.reduce((a, b) => a.ap(b), Applicative.of(f));
const parallelCollectingFailures = (f, tasks) =>
parallel(tasks.map(toTaskOfValidations))
.map(liftA(Validation, f))
.chain(validation => validation.fold(Task.rejected, Task.of)); And example usage would be like this: const getDataFromAPI = a => Task.of('data');
const getLogs = b => Task.rejected('logs');
const getCustomer = c => Task.rejected('customer');
const mergeResults = data => logs => customer => ({ data, logs, customer });
const userId = 1;
parallelCollectingFailures(mergeResults, [
getDataFromAPI(userId),
getLogs(userId),
getCustomer(userId)
]).fork(
error => console.log('error', error),
value => console.log('ok', value)
); Which should give you |
Thank you for the detailed example. Indeed is very hard to do it.
This second example gives either all the errors, or a single success, so
I'll be only able to tell the user what errors happened without providing
the succeeded data.
In the context of the example, I would like to provide as the final result
an object like so:
```
{
logs: "Error, can't connect to database",
customer: "Error, can't connect to Stripe",
data: "data"
}
```
|
[Edit]
Another option is to convert a rejected task into a resolved one. // here you would have
// f, g :: () -> Task () b
// but you can also use a `bimap` to make it something link
// f, g :: () -> Task () (Validation a b)
const validate = task => task.map(Success).orElse(compose(Task.of, Failure));
const f = () => validate(Task.of(1));
const g = () => validate(Task.rejected(2));
parallel([f(), g()]).fork(/* unused */, data => ...);
// data = [ folktale:Validation.Success({ value: 1 }),
// folktale:Validation.Failure({ value: 2 }) ] |
Oh! So you want individual results rather than the overall results. That's easier. With: const Task = require('data.task');
const Validation = require('data.validation');
const { parallel } = require('control.async')(Task);
const toTaskOfValidations = (task) =>
task.map(Validation.of)
.orElse(error => Task.of(Validation.Failure(error)));
const runEach = (tasks) =>
parallel(tasks.map(toTaskOfValidations)); And use it as: const getDataFromAPI = a => Task.of('data');
const getLogs = b => Task.rejected('logs');
const getCustomer = c => Task.rejected('customer');
const mergeResults = (data, logs, customer) => ({ data, logs, customer });
const userId = 1;
runEach([
getDataFromAPI(userId),
getLogs(userId),
getCustomer(userId)
]).map(xs => mergeResults(...xs))
.fork(
error => /* never happens */,
value => console.log(value)
); Here each value of the results record will be a Validation, however, so you'll need to pattern match on whether each individual result is a failure or a success. |
Great idea. Let me explore your suggestion.
Thank you very much
…On Sat, Jul 20, 2019, 10:46 AM Bruno Dias ***@***.***> wrote:
Another option is to convert a rejected task into a resolved one.
// here you would have // f, g :: () -> Task () b// but you can also use a `bimap` to make it something link// f, g :: () -> Task () (Validation a b)const f = () => v(Task.of(1));const g = () => v(Task.rejected(2));const v = task => task.map(Success).orElse(compose(Task.of, Failure));
parallel([f(), g()]).fork(/* unused */, data => ...);
// data = [ folktale:Validation.Success({ value: 1 }),// folktale:Validation.Failure({ value: 2 }) ]
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#222?email_source=notifications&email_token=ADANIASPQZPCWY2Q3UMTRADQAMQLFA5CNFSM4IE4YQK2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2NPYII#issuecomment-513473569>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ADANIAQ3VOJS4BFMTTNFSLLQAMQLFANCNFSM4IE4YQKQ>
.
|
Sorry for suddenly jump in. I've recently needed something like this, but with plain promises. |
This is a use case I haven't been able to solve.
I have 3 http requests, if one fails, I want, at least, the result of the other 2. Ideally, I would want to know what error happened.
I'm still using data.task from folktale 1, and this is how I do the requests, which fails fast (if one fails, I only get the first error with no result):
Here, I would like to have
data
andlogs
returned ifgetCustomer
API call fails.Thanks in advance
The text was updated successfully, but these errors were encountered: