-
Notifications
You must be signed in to change notification settings - Fork 27
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 yielding a chain of then
on Task
#950
base: v3
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,10 @@ import { action } from "../instructions.ts"; | |
|
||
import type { FrameResult } from "./types.ts"; | ||
import { create } from "./create.ts"; | ||
import { call } from "../call.ts"; | ||
|
||
type ThenFulfilledType<T, Then=T> = ((value: T) => Then | Promise<Then>) | undefined | null; | ||
type CatchType<Catch=never> = ((reason: any) => Catch | Promise<Catch>) | undefined | null; | ||
|
||
export function createTask<T>( | ||
frame: Frame<T>, | ||
|
@@ -32,7 +36,7 @@ export function createTask<T>( | |
return promise; | ||
}; | ||
|
||
let task = create<Task<T>>("Task", {}, { | ||
let task: Task<T> = create<Task<T>>("Task", {}, { | ||
*[Symbol.iterator]() { | ||
let frameResult = evaluate<FrameResult<T> | void>(() => frame); | ||
if (frameResult) { | ||
|
@@ -44,13 +48,38 @@ export function createTask<T>( | |
} | ||
} else { | ||
return yield* action<T>(function* (resolve, reject) { | ||
awaitResult(resolve, reject); | ||
getPromise().then(resolve, reject); | ||
}); | ||
} | ||
}, | ||
then: (...args) => getPromise().then(...args), | ||
catch: (...args) => getPromise().catch(...args), | ||
finally: (...args) => getPromise().finally(...args), | ||
then: async <Result1=T, Result2=never>(onfulfilled?: ThenFulfilledType<T, Result1>, onrejected?: CatchType<Result2>) => { | ||
type NewResult = Result1 | Result2; | ||
const newPromise = getPromise().then(onfulfilled, onrejected); | ||
const future: Future<NewResult> = create<Future<NewResult>>("Future", {}, { | ||
*[Symbol.iterator]() { | ||
return yield* call(() => newPromise); | ||
}, | ||
then: (...args) => newPromise.then(...args), | ||
catch: (...args) => newPromise.catch(...args), | ||
finally: (...args) => newPromise.finally(...args), | ||
}); | ||
return await future; | ||
}, | ||
catch: async (...args) => { | ||
return await task.then(undefined, ...args); | ||
}, | ||
finally: async (onfinally) => { | ||
const newPromise = getPromise().finally(onfinally); | ||
const future: Future<T> = create<Future<T>>("Future", {}, { | ||
*[Symbol.iterator]() { | ||
return yield* call(() => newPromise); | ||
}, | ||
then: (...args) => newPromise.then(...args), | ||
catch: (...args) => newPromise.catch(...args), | ||
finally: (...args) => newPromise.finally(...args), | ||
}); | ||
return await future; | ||
}, | ||
halt() { | ||
let haltPromise: Promise<void>; | ||
let getHaltPromise = () => { | ||
|
@@ -71,7 +100,7 @@ export function createTask<T>( | |
} | ||
}); | ||
}; | ||
return create<Future<void>>("Future", {}, { | ||
const future: Future<void> = create<Future<void>>("Future", {}, { | ||
*[Symbol.iterator]() { | ||
let result = evaluate<FrameResult<T> | void>(() => frame); | ||
|
||
|
@@ -86,10 +115,37 @@ export function createTask<T>( | |
}); | ||
} | ||
}, | ||
then: (...args) => getHaltPromise().then(...args), | ||
catch: (...args) => getHaltPromise().catch(...args), | ||
finally: (...args) => getHaltPromise().finally(...args), | ||
// then: (...args) => getHaltPromise().then(...args), | ||
then: async <Result1=void, Result2=never>(onfulfilled?: ThenFulfilledType<void, Result1>, onrejected?: CatchType<Result2>) => { | ||
type NewResult = Result1 | Result2; | ||
const newPromise = getHaltPromise().then(onfulfilled, onrejected); | ||
const future: Future<NewResult> = create<Future<NewResult>>("Future", {}, { | ||
*[Symbol.iterator]() { | ||
return yield* call(() => newPromise); | ||
}, | ||
then: (...args) => newPromise.then(...args), | ||
catch: (...args) => newPromise.catch(...args), | ||
finally: (...args) => newPromise.finally(...args), | ||
}); | ||
return await future; | ||
Comment on lines
+120
to
+129
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: the code here for handling the abort future is mostly the same as the This is why I think maybe it's better if we have a more general way to create a There is, to some extent, a conceptual overlap between what this block of code is trying to do and what |
||
}, | ||
catch: async (...args) => { | ||
return await task.then(undefined, ...args); | ||
}, | ||
finally: async (onfinally) => { | ||
const newPromise = getHaltPromise().finally(onfinally); | ||
const future: Future<void> = create<Future<void>>("Future", {}, { | ||
*[Symbol.iterator]() { | ||
return yield* call(() => newPromise); | ||
}, | ||
then: (...args) => newPromise.then(...args), | ||
catch: (...args) => newPromise.catch(...args), | ||
finally: (...args) => newPromise.finally(...args), | ||
}); | ||
return await future; | ||
}, | ||
}); | ||
return future; | ||
}, | ||
}); | ||
return task; | ||
|
@@ -105,4 +161,4 @@ function getResult<T>(result: FrameResult<T>): Result<T> { | |
} else { | ||
return result.exit.result; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,7 +53,30 @@ export interface Operation<T> { | |
* things, if the operation resolves synchronously, it will continue within the | ||
* same tick of the run loop. | ||
*/ | ||
export interface Future<T> extends Promise<T>, Operation<T> {} | ||
export interface Future<T> extends Promise<T>, Operation<T> { | ||
/** | ||
* Attaches callbacks for the resolution and/or rejection of the Promise. | ||
* @param onfulfilled The callback to execute when the Promise is resolved. | ||
* @param onrejected The callback to execute when the Promise is rejected. | ||
* @returns A Promise for the completion of which ever callback is executed. | ||
*/ | ||
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Future<TResult1 | TResult2>; | ||
|
||
/** | ||
* Attaches a callback for only the rejection of the Promise. | ||
* @param onrejected The callback to execute when the Promise is rejected. | ||
* @returns A Promise for the completion of the callback. | ||
*/ | ||
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Future<T | TResult>; | ||
|
||
/** | ||
* Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The | ||
* resolved value cannot be modified from the callback. | ||
* @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). | ||
* @returns A Promise for the completion of the callback. | ||
*/ | ||
finally(onfinally?: (() => void) | undefined | null): Future<T>; | ||
Comment on lines
+57
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: these three are copy-pasted from the definition of |
||
} | ||
|
||
/** | ||
* A handle to a concurrently running operation that lets you either use the | ||
|
@@ -139,7 +162,7 @@ export interface Task<T> extends Future<T> { | |
* children. | ||
* | ||
* Any errors raised by the `halt()` operation only represent problems that | ||
* occured during the teardown of the task. In other words, `halt()` can | ||
* occurred during the teardown of the task. In other words, `halt()` can | ||
* succeed even if the task failed. | ||
* | ||
* @returns a future that only resolves when all shutdown associated with this | ||
|
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.
Note: there is one very subtle change introduced by this PR: the result of these is no longer a
Promise
and, instead, is now aPromiseLike
. In practice, this should work everywhere (the goal of PromiseLike is that they're meant to be supported by everything magically), but it is possible to write code that only works on Promises and not PromiseLike (ex: any code that usesinstanceof Promise
)