Make Cordova wait for async hook to finish - node.js

In my Cordova project, I have a hook which does RequireJS optimization (r.js) on after_prepare. That optimization is inherently asynchronous, so my hook code returns before all optimization is fully finished.
For example, this causes issues when running cordova run browser: On the first page load, optimization has not finished yet and the site looks broken.
Is there a way to make the Cordovoa build process to block until a certain hook fires a callback? Or can the optimizer be run in a blocking/sync way?
An alternative I could think of is using a different process for optimization and busy-wait in the main for it to finish, but that looks like an overkill and bad practice to me.

You can use the built-in promise module to block Cordova from proceeding until the hook has resolved.
Something along these lines:
#!/usr/bin/env node
var deferral;
function doSomethingAsync(){
somethingAync
.success(function(){
deferral.resolve();
})
.fail(function(err){
deferral.reject(err);
});
}
module.exports = function(ctx) {
deferral = ctx.requireCordovaModule('q').defer();
doSomethingAsync();
return deferral.promise;
};

You don't need to call context, you can simply return a Promise from within the module.exports function
module.exports = context => {
return new Promise(resolve => {
setTimeout(() => resolve(), 1000);
});
};
I tested and it works. The problem arises because in Cordova versions => 9 you cannot use context.requireCordovaModule('q')
If you don't want to use Promise, just do
module.exports = context => {
var deferral = require('q').defer();
doSomethingAsync(() => {
deferral.resolve();
});
return deferral.promise;
};

Related

Best way to test rollback after promise rejection?

I often have situations where the behavior I'm trying to achieve is like this:
User takes action
Website updates UI optimistically
Website fires update to server
Website awaits server response
If the update fails, website rolls back UI change
I've often found myself adding multiple timeouts to my tests to try to both assert that the optimistic update was made and then gets rolled back after rejection. So something like this:
it('rolls back optimistic update', async () => {
jest.mocked(doUpdate).mockReturnValue(new Promise((resolve, reject) => {
setTimeout(reject, 1000);
});
render(<App />)
await screen.findByText('not done')
userEvent.click(screen.getByText('do it'))
await screen.findByText('Done!')
await screen.findByText('not done')
});
But this has some pretty big downsides:
Setting up tests like this is fidgety and difficult.
Using timeouts inside my tests results in tests that are ensured to be slower than they need to be.
Any significant changes to the test environment or tools have a high chance of breaking these tests.
Tests get even slower and more complicated once I need to test things like ensuring that subsequent user actions don't clobber previous actions.
If the promise happens to resolve after the test ends, I often end up with React complaining about updates not being wrapped in act.
How can I test this type of behavior in a way that is as efficient as possible and robust against test environment changes?
I now use this helper function in situations like this, where I want to control the precise order of events and promises are involved:
export default function loadControlledPromise(mock: any) {
let resolve: (value?: unknown) => void = () => {
throw new Error('called resolve before definition');
};
let reject: (value?: unknown) => void = () => {
throw new Error('called reject before definition');
};
const response = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
jest.mocked(mock).mockReturnValue(response);
return { resolve, reject };
}
Using this helper function, the example test becomes:
it('rolls back optimistic update', async () => {
const { reject } = loadControlledPromise(doUpdate);
render(<App />)
await screen.findByText('not done')
userEvent.click(screen.getByText('do it'))
await screen.findByText('Done!')
reject();
await screen.findByText('not done')
});
This ensures:
The test doesn't spend more time waiting than necessary.
Changes in the test environment are less likely to break the test.
More-complicated sequences of events can be tested without resulting in incomprehensible test code.
I can force promises to resolve before the test ends. even when I need to test conditions prior to the promise resolving, helping to avoid React complaining about updates happening outside of act.

Promise.await() vs. Top-Level Await?

This does not work:
function myFunction(myObject){
let IM = await connectors.myModel.update({
myField: true,
}, {
where: {id: myObject.id},
returning: true,
});
}
But this does work:
function myFunction(myObject){
let IM = Promise.await(connectors.myModel.update({
myField: true,
}, {
where: {id: myObject.id},
returning: true,
}));
}
I understand that you can't use await outside of an async function. What's the difference between Promise.await() and the upcoming node.js feature, Top-Level await?
Fibers and Promise.await
Meteor uses fibers (coroutines, repo here) under the hood to support asynchronous programming. This is why you can write in Meteor synchronous-style code on the server, allthough it may be async in nature:
const doc = MyCollection.findOne()
doc.someProp // yeay it's there without await
Same goes with Promise.await, which uses the current fiber as execution environment. You can run the following code in a Meteor Method:
Meteor.methods({
test () {
const someAsyncFn = async () => 'foo'
const foo = Promise.await(someAsyncFn())
return foo // 'foo'
}
})
Top-Level await and native async/await
Now you amy wonder why in 2022 Meteor still won't use real antive async/await. Simply it's a 10 year old framework with high stability and backwards compatibility. The step towards native async/await requires to drop fibers, which itself is deeply built into the core of Meteor!
However, the discussion to move to native async/await already led to development of it: https://github.com/meteor/meteor/discussions/11505
With upcoming Meteor 2.8 there will be the first native async/await support.
Top-Level async will be available after that and is still wip.
What should I do?
For now you should start slowly rewriting code to async/await with beginning of Meteor 2.8 and try to avoid Promise.await unless not possible otherwise.
You can already write server methods async-style without affecting behaviour that much:
Meteor.methods({
test: async function () {
const someAsyncFn = async () => 'foo'
const foo = await someAsyncFn()
return foo // 'foo'
}
})
For now you can't use top-level await and need to wrap it in an IIFE block:
(async () => {
})()
.then(res => {})
.catch(e => {})
It looks like it's because Meteor, my build tool, is doing something cool with Promises.
Promise.await takes an array of promises, and resolves when they all resolve, returning and array of results when it resolves.
Top level await lets you call await as if your entire global code was inside an async block.
Your code works because your are not awaiting Promise.await. You should be, or calling then.
Put your code in an IIFE block if you want to await at the top level.
;(async function() {
await foo();
})();

Issues running async tasks in Gulp

I have minimal experience with Node.js projects and have inherited one which uses Gulp to build and distribute itself.
The project has several disparate task files, and a gulpfile.js which references them all. For brevity, I will use the smallest function as an example.
//gulpfile.js
...
gulp.task('csscompile', function() {
gulp.src('./src/ops/gulp/csscompile.js')
});
...
//csscompile.js
...
module.exports = function (gulp) {
gulp.task('csscompile', function (done) {
let paths = [
'src/app/**/*.scss',
'node_modules/angular-material/angular-material.scss',
];
gulp.src(paths)
.pipe(sass())
.pipe(concat('app.css'))
.pipe(gulp.dest('build'))
.done();
});
};
Running $ gulp csscompile from the command line always completes the entire function in about 10ms, and I get output like:
[10:44:43] The following tasks did not complete: csscompile
[10:44:43] Did you forget to signal async completion?
Obviously there is an issue with waiting for the functions to complete. After searching around I have tried every imaginable combination of using async flags, using function (done), and more, to no avail. Even putting the csscompile function directly in the csscompile task does not work.
I assume using gulp.src() is part of the issue, I don't know whether those functions are inlined or what. I also want to avoid having to turn every one of these functions into a Promise.
Does anyone have any recommendations of changes I can make?
According to the docs:
When you see the "Did you forget to signal async completion?" warning, none of the techniques mentioned above were used. You'll need to use the error-first callback or return a stream, promise, event emitter, child process, or observable to resolve the issue.
So you need to return the stream.
This should work:
module.exports = function (gulp) {
gulp.task('csscompile', function () {
let paths = [
'src/app/**/*.scss',
'node_modules/angular-material/angular-material.scss',
];
return gulp.src(paths)
.pipe(sass())
.pipe(concat('app.css'))
.pipe(gulp.dest('build'));
});

How can I test that a promise have been waited for (and not just created) using Sinon?

Let's say I have a function:
const someAction = async(): Promise<string> => {
/* do stuff */
};
And I have some code that just needs to run this action, ignoring the result. But I have a bug - I don't want for action to complete:
someAction();
Which, instead, should've been looked like this:
await someAction();
Now, I can check that this action was ran:
const actionStub = sinon.stub(someAction);
expect(actionStub).to.have.been.calledWith();
But what's the most concise way to check that this promise have been waited on?
I understand how to implement this myself, but I suspect it must have already been implemented in sinon or sinon-chai, I just can't find anything.
I can certainly say that nothing like this exists in sinon or sinon-chai.
This is a difficulty inherent to testing any promise-based function where the result isn't used. If the result is used, you know the promise has to be resolved before proceeding with said result. If it is not, things get more complex and kind of outside of the scope of what sinon can do for you with a simple stub.
A naive approach is to stub the action with a fake that sets some variable (local to your test) to track the status. Like so:
let actionComplete = false;
const actionStub = sinon.stub(someAction).callsFake(() => {
return new Promise((resolve) => {
setImmediate(() => {
actionComplete = true;
resolve();
});
});
});
expect(actionStub).to.have.been.calledWith();
expect(actionComplete).to.be.true;
Of course, the problem here is that awaiting any promise, not necessarily this particular one, will pass this test, since the variable will get set on the next step of the event loop, regardless of what caused you to wait for that next step.
For example, you could make this pass with something like this in your code under test:
someAction();
await new Promise((resolve) => {
setImmediate(() => resolve());
});
A more robust approach will be to create two separate tests. One where the promise resolves, and one where the promise rejects. You can test to make sure the rejection causes the containing function to reject with the same error, which would not be possible if that specific promise was not awaited.
const actionStub = sinon.stub(someAction).resolves();
// In one test
expect(actionStub).to.have.been.calledWith();
// In another test
const actionError = new Error('omg bad error');
actionStub.rejects(actionError);
// Assuming your test framework supports returning promises from tests.
return functionUnderTest()
.then(() => {
throw new Error('Promise should have rejected');
}, (err) => {
expect(err).to.equal(actionError);
});
Some assertion libraries and extensions (maybe chai-as-promised) may have a way of cleaning up that use of de-sugared promises there. I didn't want to assume too much about the tools you're using and just tried to make sure the idea gets across.

How can I build my test suite asynchronously?

I'm trying to create mocha tests for my controllers using a config that has to be loaded async. Below is my code. However, when the mocha test is run, it doesn't run any tests, displaying 0 passing. The console.logs are never even called. I tried doing before(next => config.build().then(next)) inside of the describe, but even though the tests run, before is never called. Is there a way to have the config be loaded one time before any tests are run?
'use strict';
const common = require('./common');
const config = require('../config');
config
.build()
.then(test);
function test() {
console.log(1);
describe('Unit Testing', () => {
console.log(2);
require('./auth');
});
}
You should run Mocha with the --delay option, and then use run() once you are done building your test suite. Here is an example derived from the code you show in the question:
'use strict';
function test() {
console.log(1);
describe('Unit Testing', () => {
console.log(2);
it("test", () => {
console.log(3);
});
});
// You must use --delay for `run()` to be available to you.
run();
}
setTimeout(test, 1000);
I'm using setTimeout to simulate an asynchronous operation. Using --delay and run() allows you to build a suite that is the result of an asynchronous computation. Note, however, that the suite must be built in one shot. (You cannot have an asynchronous process inside describe that will make calls to it. This won't work.)
One thing you should definitely not do is what rob3c suggests: calling describe or it (or both) from inside a hook. This is a mistake that every now and then people make so it is worth addressing in details. The problem is that it is just not supported by Mocha, and therefore there are no established semantics associated with calling describe or it from inside a hook. Oh, it is possible to write simple examples that work as one might expect but:
When the suite becomes more complex, the suite's behavior no longer corresponds to anything sensible.
Since there are no semantics associated with this approach, newer Mocha releases may handle the erroneous usage differently and break your suite.
Consider this simple example:
const assert = require("assert");
const p = Promise.resolve(["foo", "bar", "baz"]);
describe("top", () => {
let flag;
before(() => {
flag = true;
return p.then((names) => {
describe("embedded", () => {
for (const name of names) {
it(name, () => {
assert(flag);
});
}
});
});
});
after(() => {
flag = false;
});
it("regular test", () => {
assert(flag);
});
});
When we run it, we get:
top
✓ regular test
embedded
1) foo
2) bar
3) baz
1 passing (32ms)
3 failing
// [stack traces omitted for brevity]
What's going on here? Shouldn't all the tests pass? We set flag to true in the before hook for the top describe. All tests we create in it should see flag as true, no? The clue is in the output above: when we create tests inside a hook, Mocha will put the tests somewhere but it may not be in a location that reflects the structure of the describe blocks in the code. What happens in this case is that Mocha just appends the tests created in the hook the the very end of the suite, outside the top describe, so the after hook runs before the dynamically created tests, and we get a counter-intuitive result.
Using --delay and run(), we can write a suite that behaves in a way concordant with intuition:
const assert = require("assert");
const p = Promise.resolve(["foo", "bar", "baz"]).then((names) => {
describe("top", () => {
let flag;
before(() => {
flag = true;
});
after(() => {
flag = false;
});
describe("embedded", () => {
for (const name of names) {
it(name, () => {
assert(flag);
});
}
});
it("regular test", () => {
assert(flag);
});
});
run();
});
Output:
top
✓ regular test
embedded
✓ foo
✓ bar
✓ baz
4 passing (19ms)
In modern environments, you can use top-level await to fetch your data up front. This is a documented approach for mocha: https://mochajs.org/#dynamically-generating-tests
Slightly adapting the example from the mocha docs to show the general idea:
function fetchData() {
return new Promise((resolve) => setTimeout(resolve, 5000, [1, 2, 3]));
}
// top-level await: Node >= v14.8.0 with ESM test file
const data = await fetchData();
describe("dynamic tests", function () {
data.forEach((value) => {
it(`can use async data: ${value}`, function () {
// do something with data here
});
});
});
This is nice as it is on a per-file basis, and doesn't involve you taking on management responsibility of the test runner as you do with --delay.
The problem with using the --delay command line flag and run() callback that #Louis mentioned in his accepted answer, is that run() is a single global hook that delays the root test suite. Therefore, you have to build them all at once (as he mentioned), which can make organizing tests a hassle (to say the least).
However, I prefer to avoid magic flags whenever possible, and I certainly don't want to have to manage my entire test suite in a single global run() callback. Fortunately, there's a way to dynamically create the tests on a per-file basis, and it doesn't require any special flags, either :-)
To dynamically create It() tests in any test source file using data obtained asynchronously, you can (ab)use the before() hook with a placeholder It() test to ensure mocha waits until before() is run. Here's the example from my answer to a related question, for convenience:
before(function () {
console.log('Let the abuse begin...');
return promiseFn().
then(function (testSuite) {
describe('here are some dynamic It() tests', function () {
testSuite.specs.forEach(function (spec) {
it(spec.description, function () {
var actualResult = runMyTest(spec);
assert.equal(actualResult, spec.expectedResult);
});
});
});
});
});
it('This is a required placeholder to allow before() to work', function () {
console.log('Mocha should not require this hack IMHO');
});

Resources