Skip to content

Commit d14b845

Browse files
Merge pull request #51 from andrewMacmurray/simplify-pool-id
Simplify pool id internals, allow manual assignment
2 parents b4ee977 + beb4fac commit d14b845

File tree

4 files changed

+234
-367
lines changed

4 files changed

+234
-367
lines changed

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -368,10 +368,9 @@ const tasks = {
368368

369369
## Re-using ports
370370

371-
Each `send` and `receive` port pair only support **one** `ConcurrentTask.Pool` subscribed at a time.
372-
**Weird** things can happen if you have **two or more** `ConcurrentTask.Pool`s using the same ports at the same time.
371+
For most cases a single `ConcurrentTask.Pool` is paired a `send` and `receive` port pair.
373372

374-
Generally this should not be needed, but if you have a use-case, please leave an [issue](https://github.com/andrewMacmurray/elm-concurrent-task/issues).
373+
If you need multiple `Pool`s subscribed to a port pair at the same time or need to continually instantiate new `Pool`s (e.g. switching between pages), make sure each pool has a unique id with `ConcurrentTask.withPoolId`. Otherwise `Pool`s may receive results for other pools which can lead to some confusing states!
375374

376375
## Develop Locally
377376

runner/index.ts

Lines changed: 39 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,10 @@ export * from "./http/index.js";
1313
export * from "./browser/index.js";
1414

1515
export interface ElmPorts {
16-
send: {
17-
subscribe: (
18-
callback: (defs: TaskDefinition[] | Command) => Promise<void>
19-
) => void;
20-
};
21-
receive: { send: (result: TaskResult[] | PoolId) => void };
16+
send: { subscribe: (callback: (defs: TaskDefinition[]) => Promise<void>) => void; };
17+
receive: { send: (result: TaskResult[]) => void };
2218
}
2319

24-
export type Command = { command: "identify-pool" };
25-
26-
export type PoolId = { poolId: number };
27-
2820
export type Tasks = { [fn: string]: (arg: any) => any };
2921

3022
export interface TaskDefinition {
@@ -128,69 +120,49 @@ export function register(options: Options): void {
128120
const tasks = createTasks(options);
129121
const subscribe = options.ports.send.subscribe;
130122
const send = options.ports.receive.send;
131-
let poolId = 0;
132-
133-
function nextPoolId() {
134-
poolId = cycleInt({ max: 1000 }, poolId);
135-
}
136123

137124
subscribe(async (payload) => {
138-
if ("command" in payload) {
139-
switch (payload.command) {
140-
case "identify-pool": {
141-
// The Promise.resolve wrapper here prevents a race condition in Elm where sometimes the Model is not updated in time before the poolId is received
142-
return Promise.resolve().then(() => {
143-
send({ poolId });
144-
nextPoolId();
145-
});
146-
}
147-
default: {
148-
throw new Error(`Unrecognised internal command: ${payload}`);
149-
}
150-
}
151-
} else {
152-
const debouncedSend = debounce(send, debounceThreshold(payload));
153-
154-
for (const def of payload) {
155-
if (!tasks[def.function]) {
156-
return debouncedSend({
157-
attemptId: def.attemptId,
158-
taskId: def.taskId,
159-
result: {
160-
error: {
161-
reason: "missing_function",
162-
message: `${def.function} is not registered`,
163-
},
125+
const debouncedSend = debounce(send, debounceThreshold(payload));
126+
127+
for (const def of payload) {
128+
if (!tasks[def.function]) {
129+
return debouncedSend({
130+
attemptId: def.attemptId,
131+
taskId: def.taskId,
132+
result: {
133+
error: {
134+
reason: "missing_function",
135+
message: `${def.function} is not registered`,
164136
},
165-
});
166-
}
137+
},
138+
});
167139
}
140+
}
168141

169-
payload.map(async (def) => {
170-
try {
171-
logTaskStart(def, options);
172-
const result = await tasks[def.function]?.(def.args);
173-
logTaskFinish(def, options);
174-
debouncedSend({
175-
attemptId: def.attemptId,
176-
taskId: def.taskId,
177-
result: { value: result },
178-
});
179-
} catch (e) {
180-
debouncedSend({
181-
attemptId: def.attemptId,
182-
taskId: def.taskId,
183-
result: {
184-
error: {
185-
reason: "js_exception",
186-
message: `${e.name}: ${e.message}`,
187-
raw: e,
188-
},
142+
payload.map(async (def) => {
143+
try {
144+
logTaskStart(def, options);
145+
const result = await tasks[def.function]?.(def.args);
146+
logTaskFinish(def, options);
147+
debouncedSend({
148+
attemptId: def.attemptId,
149+
taskId: def.taskId,
150+
result: { value: result },
151+
});
152+
} catch (e) {
153+
debouncedSend({
154+
attemptId: def.attemptId,
155+
taskId: def.taskId,
156+
result: {
157+
error: {
158+
reason: "js_exception",
159+
message: `${e.name}: ${e.message}`,
160+
raw: e,
189161
},
190-
});
191-
}
192-
});
193-
}
162+
},
163+
});
164+
}
165+
});
194166
});
195167
}
196168

@@ -271,7 +243,3 @@ function prefixWith(prefix: string, tasks: Tasks): Tasks {
271243
Object.entries(tasks).map(([key, fn]) => [`${prefix}${key}`, fn])
272244
);
273245
}
274-
275-
function cycleInt(options: { max: number }, i: number): number {
276-
return i >= options.max ? 0 : i + 1;
277-
}

src/ConcurrentTask.elm

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module ConcurrentTask exposing
99
, race
1010
, batch, sequence
1111
, map, andMap, map2, map3, map4, map5
12-
, attempt, attemptEach, Response(..), UnexpectedError(..), onProgress, Pool, pool
12+
, attempt, attemptEach, Response(..), UnexpectedError(..), onProgress, Pool, pool, withPoolId
1313
)
1414

1515
{-| A Task similar to `elm/core`'s `Task` but:
@@ -194,7 +194,7 @@ Here's a minimal complete example:
194194
, subscriptions = subscriptions
195195
}
196196
197-
@docs attempt, attemptEach, Response, UnexpectedError, onProgress, Pool, pool
197+
@docs attempt, attemptEach, Response, UnexpectedError, onProgress, Pool, pool, withPoolId
198198
199199
-}
200200

@@ -1264,3 +1264,23 @@ Right now it doesn't expose any functionality, but it could be used in the futur
12641264
pool : Pool msg
12651265
pool =
12661266
Internal.pool
1267+
1268+
1269+
{-| Add an id to a `Pool`.
1270+
1271+
Why? Because Pools can be instantiated multiple times (think switching pages in a Single Page App),
1272+
without a unique identifier a ConcurrentTask Pool may end up receiving responses for a ConcurrentTask pool that was previously discarded.
1273+
1274+
One example is a user switching back and forth between two pages:
1275+
1276+
- Page one has a long running task on `init`
1277+
- The user switches to page 2, then switches back to page 1
1278+
- A new long running task is started
1279+
- But the Pool can receive the response from the first long running task (which is unexpected behaviour)
1280+
1281+
Adding a different id to the `Pool` allows these previous responses to be ignored.
1282+
1283+
-}
1284+
withPoolId : Int -> Pool msg -> Pool msg
1285+
withPoolId =
1286+
Internal.withPoolId

0 commit comments

Comments
 (0)