-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
[RFC] Port loopback-phase to TypeScript for LoopBack 4 #1671
Conversation
72d8eb9
to
0d9b816
Compare
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 am not sure if phases is the right design approach, but don't mind experimenting in this area.
I think this work is out of 4.0 GA scope, could you please confirm?
```ts | ||
async (ctx, chain) => { | ||
const start = process.hrtime(); | ||
await chain!.next(); |
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.
Please find a better type description for the callback function, one that does not require users to use !
operator. If the framework always provide chain
value, then this fact should be encoded in the type signature of the callback.
async (ctx, chain) => { | ||
if (ctx.req.url === '/status') { | ||
ctx.response = {status: 'ok'}; | ||
chain!.return(); |
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 find return
as ambiguous. Can we use a name that better expresses the intent please? For example: abort
, terminate
or stop
.
|
||
There is also a `failFast` option to control how to handle errors. If `failFast` | ||
is set to `false`, errors will be caught and set on the `ctx.error` without | ||
aborting the handler chain. |
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 find this rather dangerous. I expect that most people writing phase-handlers are not going to be aware of the failFast:false
mode. As a result, failFast:false
mode can execute handlers that are not prepared to handle the situation left after one of the previous handlers failed.
I think a better solution would be to enable failFast:false
mode on a per-handler basis - i.e. allow each handler to signal whether it wants to be executed even if there was an error up in the chain.
What is the use case you are envisioning for failFast:false
mode?
Can we perhaps leave this feature out of this initial pull request, to get it landed faster?
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 introduce failFast:false
mode mostly for error phase so that we can have a list of error handlers to mediate errors.
} catch (e) { | ||
// await run handlers for errorPhase | ||
} finally { | ||
// await run handlers for finalPhase |
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.
Are we enforcing the order of $error
and $final
phase? Can users insert additional phases before, in-between or after these two built-in phases?
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.
Yes, we enforce them as catch
and finally
. No additional phases can interfere with them.
packages/phase/package.json
Outdated
{ | ||
"name": "@loopback/phase", | ||
"version": "0.1.0", | ||
"description": "LoopBack's API Explorer", |
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.
Please fix the package description.
packages/phase/src/handler.ts
Outdated
return async (ctx: Context) => { | ||
if (options.parallel) { | ||
debug('Executing handlers in parallel'); | ||
await Promise.all(handlers.map(h => h(ctx))); |
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 think it's a good idea to run all handlers in parallel.
What if we have 3 handlers registered and the handler no.2 calls ctx.return()
? It would be surprised to learn that the 3rd handler was executed to completion too!
What if we have 2 handlers registered and the first handler throws an error? I would find it surprising that the second handler was still executed. Also, are we going to wait until that second handler finishes, or will we return the error immediately?
Also, what's the point of Phase#parallel
option when we are always running all handlers in parallel?
Please add tests to capture the expected behavior and verify the implementation.
* @returns phaseNames | ||
*/ | ||
getPhaseNames(): string[] { | ||
return this.phases.map(p => p.id); |
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 we include $error
and $final
in this list?
* @param handler The handler function to register | ||
* with the given phase. | ||
*/ | ||
registerHandler(phaseName: string, handler: Handler) { |
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's the API for registering handlers for $error
and $final
phases?
.use(mockHandler('h1')) | ||
.after(mockHandler('a1')) | ||
.after(mockHandler('a2')) | ||
.use(mockHandler('h2')); |
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.
Please use a more descriptive names, I find it difficult to follow these terse two-letter identifiers.
For example: before-handlerA-1
, before-handlerA-2
, handlerA
, after-handlerA-1
, etc. handlerB
.
6c84945
to
6c1be3a
Compare
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.
Had a quick look. Didn't point out all the places the year / name needs to be updated. Had some questions to get a better understanding of this package. Will do another pass later to go over the tests.
packages/phase/src/handler.ts
Outdated
@@ -0,0 +1,154 @@ | |||
// Copyright IBM Corp. 2014. All Rights Reserved. |
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.
nitpick / reminder: copyright year and node module
need to be updated.
packages/phase/src/handler.ts
Outdated
/** | ||
* Context for the invocation of handlers by phase | ||
*/ | ||
export interface Context { |
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.
As a general cleanup of this file I think it might be best to introduce a interfaces.ts
or types.ts
which defines all the interfaces since they are cluttering this file imo.
packages/phase/src/index.ts
Outdated
@@ -0,0 +1,8 @@ | |||
// Copyright IBM Corp. 2018. All Rights Reserved. | |||
// Node module: loopback-phase |
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.
reminder: update module name.
packages/phase/src/phase-list.ts
Outdated
@@ -0,0 +1,320 @@ | |||
// Copyright IBM Corp. 2018. All Rights Reserved. | |||
// Node module: loopback-phase |
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.
update module name
* @param phase The phase to be added | ||
*/ | ||
add(phase: string | Phase) { | ||
return this.addAll(phase)[0]; |
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.
Most implementations I've seen, the logic usually lives in the singular
form of the function and the plural form is the one that calls the singular form as it loops over the entries. I'm curious to learn as to why this implementation is flipped in this particular case.
|
||
### Handler | ||
|
||
A handler is a function that takes a `Context` object, an optional |
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.
Till I dug into the code, I found the usage of the work Context
here confusing as I thought this was referring to @loopback/context
.
@raymondfeng what's the status of this pull request - is it still relevant? |
5e57668
to
7654914
Compare
This issue has been marked stale because it has not seen activity within six months. If you believe this to be in error, please contact one of the code owners, listed in the |
Closing it to favor middleware chain. |
This PR ports
loopback-phase
to TypeScript with improvements:try - catch - finally
This package can be used as the foundation for the use cases such as:
Checklist
npm test
passes on your machinepackages/cli
were updatedexamples/*
were updated