I had a function that did three different tasks, which worked fine. For better re-usability I tried to separate them into three independent hooks.
They look like this:
module.exports = function(options = {}) {
return function hookFunction(hook) {
//do stuff and set variables
function(hook.params.var){ ... } // only in second and third
hook.params.var = var; // only in first and second
return Promise.resolve(hook);
};
};
My service.hook file contains this:
module.exports = {
before: {
find: [ firstHook(), secondHook(), thirdHook() ]}}
Now they seem to run simultaneously, which causes the third to throw an error caused by missing data from first and second. I want them to run one after another. How could I achieve that?
(I tried to use .then in service.hook but it throws TypeError: hookFunction(...).then is not a function.)
I've read How to run synchronous hooks on feathersjs but I don't quite know where to place the chaining - in the third hook or in the service hook or elsewhere?
Hooks will run in the order they are registered and if they return a promise (or are an async function) it will wait for that promise to resolve. The most common mistake if they run at once but should run in sequence is that no promise or the wrong promise is being returned but it is not clear from your code example if that is the case. The following example will work and wait one second each, then print the current name and move to the next:
function makeHook(name) {
return function(context) {
return new Promise(resolve => {
setTimeout(() => {
console.log(`Running ${name}`);
resolve(context);
}, 1000);
});
}
}
module.exports = {
before: {
find: [ makeHook('first'), makeHook('second'), makeHook('third') ]}}
Related
Below is a minimized code snippet which causes the async warning in VS code (most recent version 1.70.). It is part of a load balancing algorithm which is to execute asynchronous tasks.
When the generateTask function returns a promise directly the warning disappears. When the Promise.all promise is returned I get the warning "await has no effect" (w1). Despite the warning the code behaves as wanted and waits until tasks.reduce is ready with the tasks array (which has just one task here of course for testing).
The function of the code is ok. So I am wondering whether that is a problem of the Javascript parser of VS Code. When I put the same code snippet into the script part of an HTML file VS Code does not produce the warning. As well if I put it to a ts Typescript file.
Any idea to that would be appreciated
// nodejs 16 test function. VSCode 1.70.1 IDE
async function jobExecutorMinified() {
const generateTask = () => {
return () => {
const p = new Promise(resolve => {
setTimeout(() => {
resolve();
}, 5000);
});
// returning the promise of Promise.all -> warning w1
return Promise.all([p]);
// returning the promise directly -> no warnings
// return p;
}
}
const tasks = [generateTask()];
/*w1*/await tasks.reduce((prevTask, currTask) => {
return prevTask.then(() => {
return currTask();
});
}, Promise.resolve());
console.log('task chunking is ready');
}
jobExecutorMinified().then(() => {
console.log('job executor minified returned');
});
As discussed in the comments, this is just a false positive of the linter, which is not able to determine what reduce() returns (or probably: sees that Array.prototype.reduce is not an async function). Either give it some hints (type annotations, e.g. via jsdoc) or just ignore it.
However, you really shouldn't use reduce here. If you're using async/await anyway, just write a normal loop instead:
for (const currTask of tasks) {
await currTask();
}
I'm new to cypress and am trying to figure out how things work.
I have my own function (which calls a test controller server to reset the database). It returns a promise which completes when the DB have been successfully resetted.
function resetDatabase(){
// returns a promise for my REST api call.
}
My goal is to be able to execute it before all tests.
describe('Account test suite', function () {
// how can I call resetDb here and wait for the result
// before the tests below are invoked?
it('can log in', function () {
cy.visit(Cypress.config().testServerUrl + '/Account/Login/')
cy.get('[name="UserName"]').type("admin");
cy.get('[name="Password"]').type("123456");
cy.get('#login-button').click();
});
// .. and more test
})
How can I do that in cypress?
Update
I've tried
before(() => {
return resetDb(Cypress.config().apiServerUrl);
});
But then I get an warning saying:
Cypress detected that you returned a promise in a test, but also invoked one or more cy commands inside of that promise
I'm not invoking cy in resetDb().
Cypress have promises (Cypress.Promise), but they are not real promises, more like duck typing. In fact, Cypress isn't 100% compatible with real promises, they might, or might not, work.
Think of Cypress.Promise as a Task or an Action. They are executed sequentially with all other cypress commands.
To get your function into the Cypress pipeline you can use custom commands. The documentation doesn't state it, but you can return a Cypress.Promise from them.
Cypress.Commands.add('resetDb', function () {
var apiServerUrl = Cypress.config().apiServerUrl;
return new Cypress.Promise((resolve, reject) => {
httpRequest('PUT', apiServerUrl + "/api/test/reset/")
.then(function (data) {
resolve();
})
.catch(function (err) {
reject(err);
});
});
});
That command can then be executed from the test itself, or as in my case from before().
describe('Account', function () {
before(() => {
cy.resetDb();
});
it('can login', function () {
// test code
});
})
You can use cy.wrap( promise ), although there might still be a bug where it never times out (haven't tested).
Otherwise, you can use cy.then() (which is undocumented, can break in the future, and I'm def not doing any favors by promoting internal APIs):
cy.then(() => {
return myAsyncFunction();
});
You can use both of these commands at the top-level of spec like you'd use any command and it'll be enqueued into cypress command queue and executed in order.
But unlike cy.wrap (IIRC), cy.then() supports passing a callback, which means you can execute your async function at the time of the cy command being executed, not at the start of the spec (because expressions passed to cy commands evaluate immediately) --- that's what I'm doing in the example above.
There could be a simple answer for this but I've only ever had to use extension methods (are they even called that in JS?) in C#..
I have a 3rd party library that uses events. I need a function to be called after the event is called. I could do this easily via a promise but the 3rd party library was written before ES6 and does not have promises built in. Here's some sample code -
wRcon.on('disconnect', function() {
console.log('You have been Disconnected!')
})
Ideally I would like to be able to implement something like this -
wRcon.on('disconnect', function() {
console.log('You have been Disconnected!')
}).then(wRcon.reconnect())
So to summarize my question, how do I extend wRcon.on to allow for a promise (or some other callback method)?
Promises and events, while they seem similar on the surface, actually solve different problems in Javascript.
Promises provide a way to manage asynchronous actions where an outcome is expected, but you just don't know how long it's going to take.
Events provide a way to manage asynchronous actions where something might happen, but you don't know when (or even if) it will happen.
In Javascript there is no easy way to "interface" between the two.
But for the example you've given, there is an easy solution:
wRcon.on('disconnect', function() {
console.log('You have been Disconnected!')
wRcon.reconnect()
})
This is perhaps not as elegant as your solution, and breaks down if your intention is to append a longer chain of .then() handlers on the end, but it will get the job done.
You could wrap the connection in a way that would create a promise:
const promise = new Promise((resolve) => {
wRcon.on('disconnect', function() {
resolve();
})
}).then(wRcon.reconnect())
However, this does not seem like an ideal place to use Promises. Promises define a single flow of data, not a recurring event-driven way to deal with application state. This setup would only reconnect once after a disconnect.
Three/four options for you, with the third/fourth being my personal preference:
Replacing on
You could replace the on on the wRcon object:
const original_on = wRcon.on;
wRcon.on = function(...args) {
original_on.apply(this, args);
return Promise.resolve();
};
...which you could use almost as you showed (note the _ =>):
wRcon.on('disconnect', function() {
console.log('You have been Disconnected!');
}).then(_ => wRcon.reconnect());
...but I'd be quite leery of doing that, not least because other parts of wRcon may well call on and expect it to have a different return value (such as this, as on is frequently a chainable op).
Adding a new method (onAndAfter)
Or you could add a method to it:
const original_on = wRcon.on;
wRcon.onAndAfter = function(...args) {
wRcon.on.apply(this, args);
return Promise.resolve();
};
...then
wRcon.onAndAfter('disconnect', function() {
console.log('You have been Disconnected!');
}).then(_ => wRcon.reconnect());
...but I don't like to modify other API's objects like that.
Utility method (standalone, or on Function.prototype)
Instead, I think I'd give myself a utility function (which is not "thenable"):
const after = (f, callback) => {
return function(...args) {
const result = f.apply(this, args);
Promise.resolve().then(callback).catch(_ => undefined);
return result;
};
};
...then use it like this:
wRcon.on('disconnect', after(function() {
console.log('You have been Disconnected!');
}, _ => wRcon.reconnect()));
That creates a new function to pass to on which, when called, (ab)uses a Promise to schedule the callback (as a microtask for the end of the current macrotask; use setTimeout if you just want it to be a normal [macro]task instead).
You could make after something you add to Function.prototype, a bit like bind:
Object.defineProperty(Function.prototype, "after", {
value: function(callback) {
const f = this;
return function(...args) {
const result = f.apply(this, args);
Promise.resolve().then(callback).catch(_ => undefined);
return result;
};
}
});
...and then:
wRcon.on('disconnect', function() {
console.log('You have been Disconnected!');
}.after(_ => wRcon.reconnect()));
And yes, I'm aware of the irony of saying (on the one hand) "I don't like to modify other API's objects like that" and then showing an option modifying the root API of Function. ;-)
I'm looking for a solution to define Mocha tests after getting data asynchronously.
For now, I use gulp-webdriver to getting HTML content with Selenium.
And I want tests certain HTML tags structure.
For example, I want to get all buttons structure from an HTML page.
1° In Mocha Before(), I get buttons :
var buttons = browser.url("url").getHTML("button");
2° And after that, I want tests each button in a separate it :
buttons.forEach(function(button) { it() });
The only solution found is loading HTML and extract buttons with Gulp before launch Mocha test with data_driven or leche.withData plugin.
Do you know another solution directly in Mocha test definition?
Thanks in advance,
It doesn't seem possible to dynamically create it() tests with mocha.
I finally organise my test like this :
it('Check if all tag have attribute', function() {
var errors = [];
elements.forEach(function(element, index, array) {
var $ = cheerio.load(element);
var tag = $(tagName);
if (tag.length) {
if (!tag.attr(tagAttr)) errors.push(element);
}
});
expect(errors).to.be.empty;
}
}
You can actually create dynamic It() tests with mocha if you don't mind abusing the before() hook a bit:
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');
});
Mocha supports two ways to handle asynchronicity in tests. One way is using the done callback. Mocha will try to pass a function into all of your its, befores, etc. If you accept the done callback, it's your responsibility to invoke it when your asynchronous operation has completed.
Callback style:
before(function(done) {
browser.url("url").getHTML("button").then(function() {
done();
});
});
The other approach is to use Promises. Since your call to getHTML returns a Promise, you could just return that promise and Mocha would know to wait for the promise to settle before moving ahead with anything.
Here is an example of the Promise style:
before(function() {
return browser.url("url").getHTML("button");
});
A couple things worth noting about this:
- getHtml() returns a promise for the html buttons. Whenever the asynchronous call to getHTML completes, the function passed into the then function gets invoked and the resulting value from the call to getHTML is passed in.
- Returning that promise in the before lets mocha know that you're doing something asynchronous. Mocha will wait for that promise to settle before moving past your 'before'.
For your specific example, you might want to try is something like this:
describe('the buttons', function() {
var buttons;
before(function() {
return browser.url("url").getHTML("button").then(function(result) {
buttons = result;
};
});
it('does something', function() {
buttons.forEach(function(button) {
});
});
});
How do asynchronous tests work in Intern testing framework? I have tried to get them run exactly as in the example, but the async test passes immediately without waiting for the callback to be run.
it('should connect in 5 seconds', function () {
var dfd = this.async(5000);
conn.connect(credentials, dfd.callback(function(result) {
expect(result).to.... something
}));
}
The test passes immediately. What am I doing wrong?
dfd.callback doesn’t execute anything until it itself is executed. Keep in mind that it is designed for promise callbacks (i.e. the function passed to promise.then), not Node.js-style callbacks where the argument might be an error (i.e. function (error, result) {}). It will not check to see if an error is passed as an argument.
Without knowing what conn is, but seeing how you are passing dfd.callback as an argument to something that is not a promise, my suspicion is you are trying to use a Node.js-style callback and the call is erroring immediately. We may provide a convenience wrapper for these types of callbacks in the future to convert them to a promise interface, but until then, you probably just need to do something like this:
it('should connect in 5 seconds', function () {
var dfd = this.async(5000);
conn.connect(credentials, dfd.callback(function(error, result) {
if (error) {
throw error;
}
expect(result).to.... something
}));
});
Otherwise, without knowing what conn is and seeing what your actual assertion is, it’s too hard to say what the issue is here. So long as nothing inside the callback throws an error, the test will be considered successful.
Edit: So based on your comments above it sounds like your callback is an event listener called multiple times with different information. In this case, what you could do is something like this:
it('should connect in 5 seconds', function () {
var dfd = this.async(5000);
conn.connect(credentials, dfd.rejectOnError(function (result) {
if (result !== 'what I want') {
return;
}
expect(result).to.... something
// all other tests…
// nothing threw an error, so it is a successful test
dfd.resolve();
}));
});
dfd.rejectOnError works just like dfd.callback except it does not automatically resolve the promise; you do that yourself at the end.
Your structure is okay. dfd sets a timeout of 5 seconds for the test to succeed, then immediately tries conn.connect(), which is not part of the intern framework. If you are trying a simple XHR request, try the getUrlCallback function instead.
They have a pretty cool list of tests at this url: https://github.com/theintern/intern/wiki/Writing-Tests . Look for the two async examples.