Add support for ES6 style async boot scripts (2.x)#252
Add support for ES6 style async boot scripts (2.x)#252bajtos merged 1 commit intostrongloop:2.xfrom lehni:feature/async-boot-scripts-2.x
Conversation
|
Can one of the admins verify this patch? To accept patch and trigger a build add comment ".ok\W+to\W+test." |
|
Can one of the admins verify this patch? |
2 similar comments
|
Can one of the admins verify this patch? |
|
Can one of the admins verify this patch? |
|
@slnode ok to test |
There was a problem hiding this comment.
Hello @lehni, thank you for the pull request.
First of all, please add a test to verify your implementation.
Secondly, we usually try to support any function returning a promise, not only async functions. The trick is to check whether the function call returned an object containing a then method.
var cb = function(err) {
debug('Async function finished %s', f.path);
done(err);
};
debug('Starting function %s', f.path);
var result = f.func(app, cb);
if (result && typeof result.then === 'function') {
result.then(ok => cb(), cb);
return;
} else if (f.func.length < 2) {
debug('Sync function finished %s', f.path);
done();
}
lib/executor.js
Outdated
| }); | ||
| }; | ||
| if (isAsync) { | ||
| f.func(app).then(cb.bind(null, null)).catch(cb); |
There was a problem hiding this comment.
If cb(null) throws an error, then .catch() will invoke cb again with this error - that's not correct IMO. You should pass the error-handling-callback as the second argument of .then():
f.func(app).then(cb.bind(null, null), cb);|
This is a great feature, I am looking forward to get it added! 👍 |
|
@bajtos I will work on this improvements and unit test for them. In the meantime, can you tell me which branch I should work on? Since there are two active branches (2.x and master), and I am still using 2.x for LB 3 (master doesn't seem to load mixins correctly, I meant to create a seperate issue for this), I guess I'll work on 2.x first and then port to master when it landed? |
Yes please, this works best for me. |
|
@bajtos I've pushed a new version based on your input, along with some unit tests. PTAL |
|
Two more things:
|
bajtos
left a comment
There was a problem hiding this comment.
I needed to use an anonymous function instead of the =>, because eslint was complaining about it. I tried to update eslint to a version that supports it, but that triggered a whole row of other linting issues, so I stuck with the function for now
We are trying to keep support for Node.js 0.x versions in the 2.x branch for backwards compatibility, ES6 constructs cannot be used here. This applies to most 2.x branches in loopback projects (strong-remoting, loopback-datasource-juggler, etc.). See strongloop/loopback#2807 (comment), you may want to read through the discussion that lead us to the current approach.
The tests reference the
Promiseobject. I am not sure if that's fine? If not, then I can keep it all to thenable objects.
Sure, that's fine.
| // License text available at https://opensource.org/licenses/MIT | ||
|
|
||
| module.exports = function(app) { | ||
| return Promise.reject('Promise.reject()') |
There was a problem hiding this comment.
Our practice is to reject Promises with Error instances.
return Promise.reject(new Error('Promise.reject()'));There was a problem hiding this comment.
Also please add .js extension to the filename. I am surprised your new tests are passing despite this problem?
There was a problem hiding this comment.
I am using a bit of a workaround with scriptExtensions set to '.reject' for this test, so the failing boot script does not get loaded in all other tests, but I can then explicitly request it for that single test and produce the rejection. It's not super clean but I couldn't think of another way to achieve this goal without creating another full app (similar to the simple-app folder)... If I add .js, then it would be loaded in all boot script tests, and fail each time.
I hope it's fine?
There was a problem hiding this comment.
Actually, I think I have a better idea for how to control this. I'll do the same to test for sync exceptions also.
lib/executor.js
Outdated
| result = f.func(app, cb); | ||
| } catch (err) { | ||
| debug('Sync function failed %s', f.path, err); | ||
| error = err; |
There was a problem hiding this comment.
AFAICT, error is not used. Can we remove it please?
Also when the function exported by a boot script fails, the error should be reported back/up to the caller of boot(app). I think you should call return done(err) here.
There was a problem hiding this comment.
It was late last night... My bad. Will address it now.
|
@lehni the CI is failing on Node.js 0.10, see https://travis-ci.org/strongloop/loopback-boot/jobs/258394078. It may be caused by the fact that Node.js 0.10 does not have a built-in |
|
@bajtos oops, yeah that's why the errors didn't make sense to me probably. I will look into both issues right now. Stil learning :) |
|
@bajtos I think this is it. I responded to all your feedback and added a test for errors thrown from boot scripts. And it looks like it's passing all tests! |
lib/executor.js
Outdated
| try { | ||
| var result = f.func(app, cb); | ||
| if (result && typeof result.then === 'function') { | ||
| result.then(function() { cb && cb(); }, cb); |
There was a problem hiding this comment.
cb is always defined, the check cb && is redundant.
| describe('with boot script returning a rejected promise', function() { | ||
| before(function() { | ||
| // Tell simple-app/boot/reject.js to return a rejected promise | ||
| process.rejectPromise = true; |
There was a problem hiding this comment.
It would be probably simpler to create a different application (a set of instructions) for each of these error cases. Having said that, your approach with process flags is ok too.
|
Landed, thank you for the contribution! |
|
Released as |
|
Very cool! That went faster than I'd imagined. Thanks! |
|
Shall I port this to #253 now? |
|
Can one of the admins verify this patch? |
|
@bajtos the reason for var cb = function(err) {
done(err);
// Make sure done() isn't called twice, e.g. if a script returns a
// thenable object and also calls the passed callback.
cb = null;
};I believe it's still valid? |
@lehni Ouch! You are right. I think a better solution is to set
Yes please 👍 |
Description
This PR allows boot scripts to be defined in the ES6 async fashion, without the need for passing callbacks:
Related issues
Checklist
guide