-
-
Notifications
You must be signed in to change notification settings - Fork 58
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
Allow stopping iteration via pMap.stop
#36
Conversation
|
||
export type StopValueWrapper<NewElement> = NewElement extends any ? BaseStopValueWrapper<NewElement> : never; | ||
|
||
type MaybeWrappedInStop<NewElement> = NewElement | StopValueWrapper<NewElement>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why these 3 types? Not immediately clear why this complication is needed. We don't need to expose anything about stop
to the user in the return value. For the user, it can just be an opaque object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why these 3 types? Not immediately clear why this complication is needed.
To be honest that was the simplest way I could make it work. All attempts to simplify this further caused a test in test-d.ts
to fail. In particular, I observed that TypeScript does not realize automatically that { x: A } | { x: B }
is the same as { x: A | B }
, so I had to write this extends any ?
hack.
For the user, it can just be an opaque object.
Nice idea. Do you have any favorite way of declaring an opaque object in TypeScript? Recenly I've seen Record<never, never>
, does this sound good?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to expose anything about stop to the user in the return value. For the user, it can just be an opaque object.
Actually, on second thought, I think we have to, because as soon as the user uses return pMap.stop()
inside mapper
, TypeScript will infer the return type of mapper
differently, and we need TS to infer it to something that we can unwrap later in order to provide a correct return type for the pMap
call itself. This was quite complicated to achieve to be honest. I don't see how it would be possible if we simplify the result of .stop
to a constant type such as Record<never, never>
.
Let's merge it this way, currently, and you can open an issue to improve typings? Then if someone finds a way to do it without breaking the current TS tests I added, it can be done later. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a stop value is recieved, it could set a variable which causes future resolutions of other items to avoid adding new values or creating new calls. Then, the array can be processed and resolved.
@@ -1,3 +1,7 @@ | |||
declare const stop: unique symbol; | |||
|
|||
export type StopSymbol = typeof stop; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see StopSymbol
used anywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on your idea above of not exposing the existence of { [stop] }
in the returned object, perhaps this is indeed not needed. The reason I first added this was to have a way to expose it to the user, since for example in find-up it is also exposed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I undo this, then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I undo this, then?
Yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about passing options param for exposing stop in the returned object?
/** | ||
Whether or not to remove all holes from the result array (caused by pending mappings). | ||
|
||
@default false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any thoughts on making this true
by default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine by me. I don't have any strong opinion. Personally, I will probably set this option explicitly whenever I use .stop
, regardless of what is the default. Do you want me to change it to true
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Richienb do you have any opinion on what would be the best default value here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets change it to true
then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets change it to
true
then.
Agree, to match its behaviour with pMapSkip
which could have left holes but didn't.
/** | ||
Options to configure what `pMap` must do with any concurrent ongoing mappings at the moment `stop` is called. | ||
*/ | ||
readonly ongoingMappings?: OngoingMappingsStopOptions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if there's any way we could flatten the options object:
- value
- collpasePending
- fillPending
Needs better names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it makes sense to group them. In the future, if support for canceling promises and AbortController
is added, they might probably need options that could also be added to this group.
Do you also want better names for the way I've currently done it? Or you said "better names" only for your suggestion itself?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe something like this:
value
Value to resolve withdefaultValue
Value to set when the promise hasn't been run
What if the consumer wants it to be an empty value? Maybe this could be in the form of passing apMap.stop.empty
symbolstopRunning
Whether currently running promises should be allowed to finishremoveIncomplete
Whether empty items in the resulting array where there should have been values if all the promises had run should be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think the non-flattened way is better, because value
is about the current iteration, while everything else is about configuring what happens to the rest of the mapping itself. Maybe instead of ongoingMappings
, one of:
rest
restConfig
restOptions
whatToDoWithOngoingMappings
mappingsStillRunning
inProgressBehavior
inProgressConfig
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the consumer wants it to be an empty value? Maybe this could be in the form of passing a
pMap.stop.empty
symbol
I think this particular behavior could be left for another PR. I've never seen good uses for holes in arrays, they're an obscure feature of the language, if I'm not mistaken TypeScript for example pretends they don't exist. Sindre also said it here. Although if you found a good use case for them, I'd be interested in knowing more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, we can stick with the nested option. I'm not really sold on most of these names though. The problem is that it applies to any mappings, not just running ones, but also pending ones. For example, if the concurrency
option is used, not all remaining mappings are executing when it's stopped. I think the only one that is usable is rest
, so I guess we can go with that.
@Richienb Would you be able to help review? 🙏 |
mapper: Mapper<Element, NewElement>, | ||
options?: Options | ||
): Promise<NewElement[]>; | ||
declare const pMap: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of defining a constant do this:
export function stop(...);
export default function pMap(...);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests failed because the way the function is imported changed:
- import pMap from 'p-map';
- console.log(pMap, pMap.stop);
+ import pMap, {stop} from 'p-map';
+ console.log(pMap, stop);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per c9c0882, the symbol should be a named export called pMapStop
.
readme.md
Outdated
@@ -72,6 +99,44 @@ Default: `true` | |||
|
|||
When set to `false`, instead of stopping when a promise rejects, it will wait for all the promises to settle and then reject with an [aggregated error](https://github.com/sindresorhus/aggregate-error) containing all the errors from the rejected promises. | |||
|
|||
### pMap.stop(options?) | |||
|
|||
Creates a special object that indicates to `pMap` that iteration must stop immediately. This object should just be returned from within the mapper (and not used directly for anything). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creates a special object that indicates to `pMap` that iteration must stop immediately. This object should just be returned from within the mapper (and not used directly for anything). | |
Returns a value that can be returned from the `mapper` function provided to `pMap` to immediately stop iteration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I further reworded it taking inspiration in your text: be46075 - what do you think?
/** | ||
Options to configure what `pMap` must do with any concurrent ongoing mappings at the moment `stop` is called. | ||
*/ | ||
readonly ongoingMappings?: OngoingMappingsStopOptions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe something like this:
value
Value to resolve withdefaultValue
Value to set when the promise hasn't been run
What if the consumer wants it to be an empty value? Maybe this could be in the form of passing apMap.stop.empty
symbolstopRunning
Whether currently running promises should be allowed to finishremoveIncomplete
Whether empty items in the resulting array where there should have been values if all the promises had run should be removed.
@@ -1,3 +1,7 @@ | |||
declare const stop: unique symbol; | |||
|
|||
export type StopSymbol = typeof stop; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I undo this, then?
Yes
/** | ||
Whether or not to remove all holes from the result array (caused by pending mappings). | ||
|
||
@default false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets change it to true
then.
mapper: Mapper<Element, NewElement>, | ||
options?: Options | ||
): Promise<NewElement[]>; | ||
declare const pMap: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per c9c0882, the symbol should be a named export called pMapStop
.
|
||
export type StopValueWrapper<NewElement> = NewElement extends any ? BaseStopValueWrapper<NewElement> : never; | ||
|
||
type MaybeWrappedInStop<NewElement> = NewElement | StopValueWrapper<NewElement>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/** | ||
Options to configure what `pMap` must do with any concurrent ongoing mappings at the moment `stop` is called. | ||
*/ | ||
readonly ongoingMappings?: OngoingMappingsStopOptions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, we can stick with the nested option. I'm not really sold on most of these names though. The problem is that it applies to any mappings, not just running ones, but also pending ones. For example, if the concurrency
option is used, not all remaining mappings are executing when it's stopped. I think the only one that is usable is rest
, so I guess we can go with that.
@papb Friendly bump in case you forgot about this PR. Feel free to ignore if you're just busy ;) |
Closing to let someone else be able to work on this as this PR is not moving forward. |
Closes #31
Note: this PR also currently includes a bug fix for an edge case, see below.edit: no longer true, moved to #40