-
Notifications
You must be signed in to change notification settings - Fork 313
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
Adding Promise.prototype.finally #72
Conversation
This PR adds Promise.prototype.finally (as it is now part of ES2018). Please add a test for it and verify it is functional before merging. The code is mostly copied from https://github.com/matthew-andrews/Promise.prototype.finally/blob/master/finally.js
I've now added unit tests for Promise.prototype.finally as well, so I believe it should be safe to merge now. |
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.
@dfahlander Thanks for doing this. It looks great! I added a few comments. After you make the requested changes, I will be happy to merge this in.
src/index.js
Outdated
var constructor = this.constructor; | ||
return this.then( | ||
function(value) { | ||
return constructor.resolve(callback()).then(function() { |
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.
Based on the spec and the implementation in Chrome, if the the callback is not callable (not a function), it should not throw an error. It should return a new Promise.
if (typeof callback !== 'function') {
return Promise.resolve(value);
}
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.
Ah, nice you catched that ;) That's a change that should be updated in Promise.prototype.finally polyfill as well. I pretty much just copied that code. When thinking about it, I am also not sure that we need to do constructor.resolve() at all. We're already in a then- callback so we might be fine with just returning the result. Or am I wrong?
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 was suggesting putting that code before this.then, outside of the “then” to avoid 2 extra promises being created
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 see. The thing I was thinking about was whether it would be safe to do:
Promise.prototype.finally = function (callback) {
if (typeof callback !== 'function') {
return this.constructor.resolve(value);
}
return this.then(value => {
callback(); // Not handling return value at all
return value;
}, err => {
callback(); // Not handling return value at all
return this.constructor.reject(err);
});
}
But I see that it should not comply to the spec as it wouldn't await any promise returned by callback(), so let's forget about it...
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.
Wait a sec. How could we put the typecheck before this.then() when we don't yet have a value?
Maybe this would do?
Promise.prototype['finally'] = function(callback) {
if (typeof callback !== 'function') {
return this; // Equal to this.then(value => value);
}
var constructor = this.constructor;
return this.then(
function(value) {
return constructor.resolve(callback()).then(function() {
return value;
});
},
function(reason) {
return constructor.resolve(callback()).then(function() {
return constructor.reject(reason);
});
}
);
};
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.
To support ES3, I also put finally in brackets this time.
test/promise.js
Outdated
@@ -191,6 +191,62 @@ describe('Promise', function() { | |||
assert(prom instanceof SubClass); | |||
}); | |||
}); | |||
describe('Promise.prototype.finally', function() { | |||
it('should be called', function(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.
This test is already covered in the below tests. I think it's OK to remove it.
test/promise.js
Outdated
|
||
it('should be called on success', function(done) { | ||
Promise.resolve(3).finally(function() { | ||
assert(true, 'Finally handler was called'); |
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.
This assert is unnecessary. It never fails. Can you change this to
Promise.resolve(3).finally(function() {
assert.equal(arguments.length, 0, 'No arguments to onFinally');
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.
Actually it would fail if it was never called (by a timeout in the testing framework). But I can check there are no args provided. Should also add a new test verifying that if the handler returns a promise, it must be awaited for.
}); | ||
|
||
it('should be called on failure', function(done) { | ||
Promise.reject(new Error()).finally(function() { |
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.
This assert is unnecessary. It never fails. Can you change this to
Promise.reject(new Error()).finally(function() {
assert.equal(arguments.length, 0, 'No arguments to onFinally');
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.
Looks good. Thanks for making the change
src/index.js
Outdated
@@ -143,6 +143,25 @@ Promise.prototype.then = function(onFulfilled, onRejected) { | |||
return prom; | |||
}; | |||
|
|||
Promise.prototype['finally'] = function(callback) { | |||
if (typeof callback !== 'function') { |
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.
Sorry, I made a mistake on this. This isn't enough to find all callable types. Here is the correct implementations https://github.com/npetruzzelli/es-abstract-is-callable/blob/master/lib/es2017-v8/IsCallable.js .
I don't think writing that much code is worth it (for the goal of this library being a small library). I think removing this if
statement is a better idea. It will obviously throw an error if someone passes a non callable, but I prefer to support different types of callables.
Sorry to ask this to be changed back, I didn't fully understand the tradeoff.
Everything looks great. One last comment about my mistake about callables. |
Thanks again for doing this. Really appreciate it! |
Thanks for providing this minimal and precise promise library. Your code has been a great resource for me ever since 2014 when I wrote Dexie as I needed some minimal and easy-to-understand promise code that I could extend (needed to tweak the code to be indexedDB-compliant due to this issue. Your code has been a corner stone for Dexie is. So thanks back. http://dexie.org/docs/Promise/Promise#implementation-details |
This PR adds Promise.prototype.finally (as it is now part of ES2018).
Please test and verify it is functional before merging!I've added tests, so it should be fine to merge.The code is mostly copied from https://github.com/matthew-andrews/Promise.prototype.finally/blob/master/finally.js