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.
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.
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.
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.
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.
I don't see StopSymbol used anywhere.
There was a problem hiding this comment.
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.
Should I undo this, then?
There was a problem hiding this comment.
Should I undo this, then?
Yes
There was a problem hiding this comment.
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.
Any thoughts on making this true by default?
There was a problem hiding this comment.
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.
@Richienb do you have any opinion on what would be the best default value here?
There was a problem hiding this comment.
Lets change it to true then.
There was a problem hiding this comment.
Lets change it to
truethen.
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.
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.
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.
Maybe something like this:
valueValue to resolve withdefaultValueValue 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.emptysymbolstopRunningWhether currently running promises should be allowed to finishremoveIncompleteWhether 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.
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:
restrestConfigrestOptionswhatToDoWithOngoingMappingsmappingsStillRunninginProgressBehaviorinProgressConfig
There was a problem hiding this comment.
What if the consumer wants it to be an empty value? Maybe this could be in the form of passing a
pMap.stop.emptysymbol
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.
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.
Instead of defining a constant do this:
export function stop(...);
export default function pMap(...);There was a problem hiding this comment.
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.
Per c9c0882, the symbol should be a named export called pMapStop.
|
|
||
| ### 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.
| 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.
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.
Maybe something like this:
valueValue to resolve withdefaultValueValue 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.emptysymbolstopRunningWhether currently running promises should be allowed to finishremoveIncompleteWhether 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.
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.
Lets change it to true then.
| mapper: Mapper<Element, NewElement>, | ||
| options?: Options | ||
| ): Promise<NewElement[]>; | ||
| declare const pMap: { |
There was a problem hiding this comment.
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>; |
| /** | ||
| 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.
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