-
-
Notifications
You must be signed in to change notification settings - Fork 103
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
Add promise support #25
Conversation
It depends on what is not backwards-compatible. If we are saying that suddenly all existing middleware will no longer function, that is a really big thing and would require a really long time to determine if that is desired, get the community to update, etc. Breakage of any current middleware pattern can be done, but it just has to have a big enough benefit to justify community-wide breakage and pain. I hope that provides some kind of benchmark :) |
Layer.prototype._handle_any = function _handle_any(/* ...args, next */) { | ||
var self = this | ||
var args = Array.prototype.slice.call(arguments) | ||
var next = args.pop() |
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.
Don't pop
and concat
later, you could just use arguments[arguments.length - 1]
and apply using arguments
.
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.
Yeah that's a vestigal structure, fixed 😄
This would be a really cool addition 😄 The only thing that probably needs some discussion is the Edit: About |
} | ||
|
||
if (result != null && typeof result.then === 'function') { | ||
var onRejected = function (error) { if (!hasNextBeenCalled()) { next(error) } } |
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.
Just a note, but we should probably use next(error || new Error('something here')
as promises can be rejected with falsy values.
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.
Added an error, but maybe someone would want to reject(false)
and expect this behavior? I would not understand why so I agree with you.
My reasoning for |
@calebmer Sorry, Edit: See my previous edit. Sorry 😝 |
Yes, that is true. Do we agree that |
It's up to @dougwilson here, but I don't think it belongs in this PR. We should make a new issue that addresses multiple |
Yes, please keep a PR to only making one change at a time so discussions are easy to follow and we can accept them on independent timelines. Addressing multiple next() calls should be a different PR. |
The practical solution for this PR is here. Would you like me to move this into its own PR? |
@calebmer Yes, don't try to do it as part of this PR. It would also need to work for the normal callbacks multiple times. |
var hasNextBeenCalled = function () { return next._currentLayer === self } | ||
|
||
try { | ||
result = handle.apply(undefined, arguments) |
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.
Prior to this change, the handle
was called with the default context (global
in sloppy mode and null
in strict mode). Now this is being changed to always provide a context of undefined
. This will probably break some middleware. Was there a reason to change the context of the handle call? Can we search npm
to determine the swath of modules this change would effect?
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.
@dougwilson If I understand, this will still function the same as this is already under strict mode. Also, call
and apply
actually work the same here (when you pass undefined
in non-strict mode it would be global, but this file is strict).
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 did some basic research and undefined
appeared to be the most canonical thing to use. How would we fix this in the scenario where it might be useful to give some people the global
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.
Hi guys, what determines the context in this case is the strict mode of the handle
function, which is determined by the calling code outside of this module; the use of strict in this source code does not affect the context that handle
ultimately ends up with.
You can see this displayed right here:
$ node -e 'var a=(function(){"use strict";return function(){console.log(String(this))}}()),b=(function(){return function(){console.log(String(this))}}());(function(){"use strict";console.log("strict caller");console.log(String(this));console.log("strict callee");a();console.log("sloppy callee");b()}())'
strict caller
undefined
strict callee
undefined
sloppy callee
[object global]
This right now is definitely a breaking point of this PR.
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.
Ok, changed undefined
to (function () { return this })()
which works in theory. Here is my test:
function strict() {
'use strict';
context.fn();
}
function sloppy() {
context.fn();
}
var context = {
a: 1,
fn: function () {
console.log(this.a);
console.log((function () { return !!this })());
}
};
strict();
sloppy();
and output:
1
true
1
true
Although I'm getting a different result from you (as you can see), both are returning the global object implying the 'use strict';
in strict()
does nothing.
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.
Hi @calebmer , your change does not fix it, because the this
in your code is still executing from within the strict code in the router source code, which is not where the this
varies at.
Your test above is also very different from the test I gave, as the this
is always being executed in sloppy mode, not in the caller's defined code. Please refer to my example for what I'm talking about :)
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.
Basically, the this
is defined by where the physical this
is located, it will not vary depending on the call stack, which is what the issue is. In your test, the this
for both calls is physically located in the same spot, this why it always evaluates to the same thing. My example demonstrates that the this
is going to be physically located in other people's code, not within the router source code. Your use of .apply
overrides this behavior, which is likely to result in unexpected behavior (Node.js code experienced this a lot when they tried to change the context for setTimeout
execution).
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.
Ok, so I created a test for this, so really, all that would matter if the following test passed (ensure nowhere else in the file with the tests there is a 'use strict'
):
describe('this', function () {
describe('when middleware is sloppy', function () {
it('should have global context', function (done) {
var router = new Router()
var server = createServer(router)
router.get('/', function (req, res) {
res.end(String(this === global))
})
request(server)
.get('/')
.expect(200, 'true', done)
})
})
describe('when middleware is strict', function () {
it('should have null context', function (done) {
'use strict'
var router = new Router()
var server = createServer(router)
router.get('/', function (req, res) {
res.end(String(this === global))
})
request(server)
.get('/')
.expect(200, 'false', done)
})
})
})
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.
Added the tests verbatim and they worked with both (function () { return this })()
and undefined
interestingly enough. So I switched back to undefined
. Want to take a look?
I am going to close this for now until #26 is accepted and the new branch is created. This is so I can do development on the master branch of my fork for npm installs. |
@calebmer How were these two things tied together? I would still love to see promise support in the router. |
@blakeembrey in async/await functions a developer might call next then throw an error afterwards calling next again. Along with a couple other weird async multiple next call scenarios, it would be helpful to emit an error/prevent it from happening. That's the goal of #26 and the Mainly I'm closing (not forever 😊) so I can continue development on my master, specifically implementing some ideas from expressjs/express#2259 (koa style middleware) |
@calebmer Fair enough. I experimented with a promise attempt a while back to support upstream calls, but it was a bit of a mess to stay backward compatible. It'd be good to have it working for |
@blakeembrey if you want to follow future development while we wait for #26 to get merged it lives at calebmer/router. |
Any progress? @calebmer, @blakeembrey |
I wish, but it's out of my hands atm 😖 I'm not sure who has the power to do Express releases at this point, maybe the Node.js TSC? You'll need to dig up who has the authority to make this call and bug them. |
Moved from expressjs/express#2763
Once this is ready to merge, I want to try to implement the
next
plan from expressjs/express#2259, which would require an optionalPromise
library.Also, I'm not familiar with your policy for backwards compatibility (or if one exists at all) in express 5.0, that could be helpful to know.