Detect missing await in JavaScript methods in VSCode - javascript

I'm searching for some eslint option, or some other way to detect missing the 'await' keyword before calling async methods inside a class. Consider the following code:
const externalService = require('./external.service');
class TestClass {
constructor() { }
async method1() {
if (!await externalService.someMethod()) {
await this.method2();
}
}
async method2() {
await externalService.someOtherMethod();
}
module.exports = TestClass;
There will be no warning if I will convert method1 to:
async method1() {
if (!await externalService.someMethod()) {
this.method2();
}
}
I tried to do on the '.eslintrc' file:
"require-await": 1,
"no-return-await": 1,
But with no luck. Anyone have an idea if it is even possible?
Thanks a lot!

typescript-eslint has a rule for this: no-floating-promises
This rule forbids usage of Promise-like values in statements without handling their errors appropriately ... Valid ways of handling a Promise-valued statement include awaiting, returning, and either calling .then() with two arguments or .catch() with one argument.
As you probably figured out from the name, typescript-eslint is designed to add TypeScript support to eslint, but you can use it with JavaScript as well. I guess it's your call to decide whether it's overkill for this one rule, but here are the steps:
Generate a tsconfig.json file
npx tsc --init
Install dependencies
npm install --save-dev eslint #typescript-eslint/eslint-plugin #typescript-eslint/parser
Modify your .eslintrc file
Based on my testing it looks like you'll need these entries at a minimum:
{
"parser": "#typescript-eslint/parser",
"parserOptions": { "project": "./tsconfig.json" },
"plugins": ["#typescript-eslint"],
"rules": {
"#typescript-eslint/no-floating-promises": ["error"]
}
}
If there are places where you want to call an async function without using await, you can either:
Use the void operator as mentioned in the documentation for the rule, e.g.
void someAsyncFunction();
Or just change error to warn in the .eslintrc configuration above
The documentation for setting up typescript-eslint is here for more info: https://typescript-eslint.io/docs/linting/linting
Next time you run eslint you should see the rule applied:
$ npm run lint
...
./services/jobService.js
11:5 warning Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator #typescript-eslint/no-floating-promises
Since you mentioned VS Code specifically, this also integrates great with the ESLint plugin:

require-await says "Don't make a function async unless you use await inside it".
This is because async has two effects:
It forces the function to return a promise
It lets you use await inside it
The former is rarely useful which mean if you aren't using await inside the function you need to question why you marked it as async.
no-return-await stops you from doing:
return await something
Because await unwraps a value from a promise, but returning a value from an async function wraps it in a promise.
Since just returning a promise causes that promise to be adopted, combining return with await is just bloat.
So neither of those do what you want.
Which brings us to your actual desire.
Such a feature does not (as far as I know) exist in ESLint, and I don't think it would be useful to have one.
There are lots of use cases where you don't want to await something returned by an async function.
e.g.
const array_of_promises = array_of_values.map( value => do_something_async(value) );
const array_of_resolved_values = await Promise.all(array_of_promises);
The above is a common use-case where you want to run a bunch of async functions in parallel and then wait for them all to resolve.
Another example is the case that no-return-await is designed to detect!
Cases like these are common enough that most people wouldn't want their toolchain calling them out for doing it.

There is an ESLint plugin for this that you can use as an alternative to the typescript-eslint route:
https://github.com/SebastienGllmt/eslint-plugin-no-floating-promise

Related

Javascript promise never resolves

I am using rotateByDegrees from a library called node-poweredup in a typescript project:
motor.rotateByDegrees(20,10).then(function(value) {console.log("test");}, function(value) {console.log("error");});
I would expect to see "test" after successful completion, but the promise never resolves. If I use await, it hangs on the await line forever.
Replicating the syntax that appears to be used in the rotateByDegrees function:
let promise = new Promise((resolve) => { return resolve(); });
does not compile, I get error TS2794: Expected 1 arguments, but got 0. Did you forget to include 'void' in your type argument to 'Promise'? I can make it compile and behave as expected with resolve(true), but how does it compile in the library then? Do I misunderstand promises? Some feature in typescript? A bug in the library? I am a newbie to JavaScript, I don't want to over-complicate this question by including irrelevant details. If you can give me hints on what I am missing and how to debug this, I can provide all relevant details.
Thanks to the helpful comments I was able to narrow it down to the compilation of the library. I did in fact not use a pre-compiled binary but had to compile the library myself using electron-rebuild to make the bluetooth adapter work. I did the following test:
git clone https://github.com/nathankellenicki/node-poweredup.git
cd node-poweredup
npm install
npm run build
this compiles without error. I created the following test file
const PoweredUP = require("node-poweredup");
const poweredUP = new PoweredUP.PoweredUP();
poweredUP.scan(); // Start scanning for hubs
console.log("Looking for Hubs...");
poweredUP.on("discover", async (hub) => { // Wait to discover hubs
await hub.connect(); // Connect to hub
console.log(`Connected to ${hub.name}!`);
const motorA = await hub.waitForDeviceAtPort("A"); // Make sure a motor is plugged into port A
motorA.rotateByDegrees(20,10).then(function(value) {console.log("test");});
});
and get the expected output:
node-poweredup$ node test.js
Looking for Hubs...
Connected to MyHub2!
test
Connected to MyHub3!
test
When I changed the first line to
const PoweredUP = require(".");
to make it use my self-compiled binary I get
node-poweredup$ node test.js
Looking for Hubs...
Connected to MyHub2!
Connected to MyHub3!
Of course this is only a partial answer because I still don't know why it compiles differently on my machine, but at least I have an idea where to start searching for the problem.

Nightwatch JS command chaining doesn't work in async functions

I'm trying to call an async page object command in my Nightwatch test but the test is failing. As you can see by the code sample, I'm attempting to chain a couple of .pause commands together but the chaining mechanism is not working. If I remove the 'async' keywords and comment out the 'await' code then the chained commands work. So it seems that using 'async' breaks command chaining. Is there a solution for this?
Nightwatch test ...
module.exports = {
'Test Commmand Queue': async function (browser) {
browser.page.clients.sandbox.simpleTestPageObject().testCommandQueue() // async function
}
}
page object file ...
module.exports = {
elements: {},
commands: [{
testCommandQueue: async function () {
this
.pause(1)
.pause(1)
console.log("0001")
await this.pause(3000, function () {
console.log("0002")
})
console.log("0003")
}
}]
}
output ...
Running: Test Commmand Queue
_________________________________________________
TEST FAILURE: 1 error during execution; 0 tests failed, 0 passed (4.939s)
TypeError: this.pause(...).pause is not a function
Nightwatch v 1.5.0
As much as it looks appealing to use chaining commands I suggest use this.api or browser.
It sounds like a chore but in the long run you will encounter less issues with methods and custom commands.
For example I can't use chaining commands if I need to use the expect by Mocha so I just rather use browser

ReferenceError: You are trying to `import` a file after the Jest environment has been torn down

I have a component that makes use of Animated component from react native. I started writing a test case to simulate onPress of a component, which calls a function that has Animated.timing in it, and setState.
running jest works fine, but the tests never stops running, and one unrelated test case that I've written before never seem to pass now (which passed before).
running jest --watch, I get this error:
ReferenceError: You are trying to `import` a file after the Jest environment has been torn down.
at Function.bezier (node_modules/react-native/Libraries/Animated/src/Easing.js:113:21)
at ease (node_modules/react-native/Libraries/Animated/src/Easing.js:34:24)
at TimingAnimation._easing (node_modules/react-native/Libraries/Animated/src/Easing.js:133:18)
at TimingAnimation.onUpdate (node_modules/react-native/Libraries/Animated/src/animations/TimingAnimation.js:107:45)
RUNS src/__tests__/SlideDownMenu.test.js
/home/nrion/Desktop/mobile-ui/PriceInsight_app/node_modules/react-native/Libraries/Animated/src/Easing.js:114
return _bezier(x1, y1, x2, y2);
^
TypeError: _bezier is not a function
at Function.bezier (/home/nrion/Desktop/mobile-ui/PriceInsight_app/node_modules/react-native/Libraries/Animated/src/Easing.js:224:12)
at ease (/home/nrion/Desktop/mobile-ui/PriceInsight_app/node_modules/react-native/Libraries/Animated/src/Easing.js:94:21)
at TimingAnimation._easing (/home/nrion/Desktop/mobile-ui/PriceInsight_app/node_modules/react-native/Libraries/Animated/src/Easing.js:255:16)
at TimingAnimation.onUpdate (/home/nrion/Desktop/mobile-ui/PriceInsight_app/node_modules/react-native/Libraries/Animated/src/animations/TimingAnimation.js:138:14)
at ontimeout (timers.js:386:11)
at tryOnTimeout (timers.js:250:5)
at Timer.listOnTimeout (timers.js:214:5)
Link to repl
https://repl.it/repls/PartialGrimyMetadata
Environment:
OS: Linux 4.14
Node: 6.14.2
Yarn: 1.7.0
npm: 3.10.10
Watchman: Not Found
Xcode: N/A
Android Studio: Not Found
OK, found a solution.
Should use jest.useFakeTimers()
Note: Put the code above just after import section in your test file.
jest.useFakeTimers()
With above it's extremely important to understand this
jest.useFakeTimers() mocks out setTimeout and other timer functions with mock functions.
If running multiple tests inside of one file or describe block, jest.useFakeTimers(); can be called before each test manually or with a setup function such as beforeEach.
Not doing so will result in the internal usage counter not being reset.
Add "testEnvironment": "jsdom" into jest key in package.json or jest.config.js
"jest": {
"testEnvironment": "jsdom",
"preset": "react-native",
...
taken from: https://stackoverflow.com/a/64567257/728287
I would like to contribute to the answer.
Well let's see the error message
ReferenceError: You are trying to 'import' a file after the Jest environment has been torn down.
Torn down means: Jest already finished running and some part of your code is trying to execute after jest has already finished running the test. This is pretty common on Javascript due to its asynchronous nature.
Sometimes it happens after a promise callback was executed. For example:
import { someProcess } from 'a-library'
task.job().then(result => {
someProcess(result)
})
In the example above, the code imports someProcess from a-library.
If the method job from the task object takes longer than the jest execution, its callback (then() invocation) will run outside jest because jest has already finished running the test. Therefore when someProcess gets executed it will be loaded from a-library so jest will complain that you are trying to load a library after jest has been torn down.
The answer marked as the solution is partially right because calling jest.useFakeTimers() will prevent your code to wait the n seconds you supposed to wait when calling setTime or similar, making your code artificially synchronous.
Making your test await those method calls would help you understand better where the error is being introduced.
The code that worked for me was
describe("Some test set", () => {
let heavyWorkingService = library.workingService();
// We are marking the test function call as async
test(("Name of the test"), async () => {
// we are awaiting heavyWorkingService to finish its job
await heavyWorkingService("some data")
})
})
In my real scenario, my code was getting data from firebase and because I'm not using mocks for this test, it was trying to import firebase after the .get() data read returned. Marking the test as async and calling the method with the await keyword solved my problem
Of course there are a lot of ways to deal with this error, just wanted you to know the true reason behind this error so you can work in the right solution for your tests!
I am using #testing-library/react-native, so what I did is to cleanup in the setup file.
// jest.setup.js
import { cleanup } from '#testing-library/react-native';
afterEach(cleanup);
add the below lines at the very end of the jest test file,
after all, tests are written.
afterAll(() => {
mongoose.connection.close()
})
and also just run a single test file at a time, for example,
> jest --verbose --runInBand -- filename.test.js
None of above works for me. My solution is to add to jest-setup.js file this code:
import { Animated } from 'react-native';
Animated.timing = () => ({
start: () => jest.fn(),
});
Don't forget to include this file to your package.json jest configuration:
...
"jest": {
...,
"setupFiles": [
"<rootDir>/src/tests/jest-setup.js"
]
},
...
I hope it will help anybody
jest.useFakeTimers was not an option for me since it brakes the execution of other libraries I am using.
My workaround was to simply add a delay after each test, so that any async operation has time to complete before Jest environment is torn down:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
describe('Test suite', () => {
// Give time to any async operation to complete after each test
afterEach(async () => {
await sleep(2000);
});
// Also close any pending connection (or related) if any
afterAll(async () => {
await closeConnections();
});
it('My test', async () => {
// Test...
});
});
Of course the delay time must be tuned for your application.
I was getting this issue while testing Apollo with react-native-testing-library.
In this case there were two queries, in a parent and child component. The parent query needed to resolve before the child rendered and fired its query.
The solution was to run the wait() function twice rather than just once. I assume the double-fire is necessary to ensure both queries run. The error message is very opaque though.
test("...", () => {
const rr = render(...);
await wait();
await wait(); // Child query must also run
expect(...);
}
// Wait a tick so the query runs
// https://www.apollographql.com/docs/react/development-testing/testing/#testing-final-state
export async function wait(ms = 0) {
await act(async () => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
});
}
I'm using #testing-library/react-native. On my test case, I need to call render(<MyComponent/>) twice so I needed to add afterEach(cleanup) that way it fixed the issue.
In my case i was using typeorm and put that code inside my jest.setup.ts
afterAll(async () => {
const connection = getConnection();
connection.close();
});
Just to add my bit to the discussion... Double-check if you have any asynchronous code in your test case.
My case was basically a lack of attention, see:
it('should not allow click', async () => {
const page = await newE2EPage();
await page.setContent(`<my-button disabled></my-button>`);
const component = await page.find('my-button');
// WRONG ! ! ! (since click() is async)
component.click();
// ✅ Correct
await component.click()
...
});
The error message isn't that straightforward to understand (at least it wasn't for me). I hope this helps someone!
I tried adding jest.useFakeTimers() inside my beforeEach() method but it didn't resolve the issue for me.
This jest.mock() call above my test cases was the culprit for me:
jest.mock('axios', () => ({
post: () => Promise.resolve({ data: 'data' }),
}));
Removing it resolved my issue.
Hope this helps someone.
Adding "testEnvironment": "jsdom" in package.json didn't helped in my case.
Then I added in the test file under all imports:
jest.useFakeTimers();
and inside describe but above the first test:
beforeEach(() => {
jest.resetAllMocks();
});
and that solved the problem for me.
IN THIS CASE THE ERROR IS SELF EXPLANATORY:
Maybe a bit late to the party but I just want to help whoever is still struggling with this. As the error says, the problem is that some part of your code is trying to import a module after the test has finished. Check the following code:
it('should do something', () => {
someAsyncFuntion(); // We arent awaiting this function so the test ends before execution
});
const someAsyncFunction = async () => {
const {getByPlaceholderText} = render(<SomeComponent />)
getByPlaceholderText('Some placeholder text')
}
With this code the error will be fired, but why? If we check the code for getByPlaceholderText we will see that it is dynamically importing the TextInput component:
const getTextInputNodeByPlaceholderText = (node, placeholder, options = {}) => {
try {
const {
TextInput
} = require('react-native'); // <--- Here we have our import
const {
exact,
normalizer
} = options;
return (0, _filterNodeByType.filterNodeByType)(node, TextInput) && (0, _matches.matches)(placeholder, node.props.placeholder, normalizer, exact);
} catch (error) {
throw (0, _errors.createLibraryNotSupportedError)(error);
}
};
So as the error says, we are trying to import a module after the test has finished.
Add this into your jest.config.js
timers: "fake",
This helps me to resolve this problem.
My jest.config.js file now looks this way
module.exports = {
preset: "react-native",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
transformIgnorePatterns: [
"node_modules/(?!(#react-native|react-native|react-native-vector-icons)/)",
],
timers: "fake",
};
In the latest versions of Jest use this one
import type { Config } from "jest";
const config: Config = {
preset: "react-native",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
testPathIgnorePatterns: [
"/node_modules/",
"<rootDir>/template",
"Libraries/Renderer",
"RNTester/e2e",
],
transformIgnorePatterns: [
"node_modules/(?!(#react-native|react-native|react-native-vector-icons)/)",
],
fakeTimers: {
enableGlobally: true,
},
verbose: true,
};
export default config;
My project is with ReactNative and TypeScipt
and my Jest config by default is in package.json.
To solve this issue i just had to add "timers": "fake"
"jest": {
"timers": "fake",
"preset": "react-native",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
]}
The reason why you need this was already explained:
Torn down means: Jest already finished running and some part of your code is trying to execute after jest has already finished running the test. This is pretty common on Javascript due to its asynchronous nature.
There's NO need to add "testEnvironment": "jsdom". As people say above; has no sense with ReactNative.
Using jest.useFakeTimers() on top of code after imports, did not work for me.
I found the below solution here
now at least, I do not see the error now but a warning message appeared at the end saying..
A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks. Active timers can also cause this, ensure that .unref() was called on them.
it('your test case', () => {...})
becomes:
it('your test case', async () => {...})
adding async removes the erroring out.
I used jest.useFakeTimers() in beforeEach() method inside test suit of that file
beforeEach(() => {
jest.useFakeTimers();
});
OR
use jest.useFakeTimers() on top of code after imports

finally block in angular 2 promise

I observe that In angular 2 there is no finally block for promise API
angular 1 :
loadUsers() {
fetch('/api/users').then((response) => {
return response.json();
}).then((data) => {
this.users = data;
}).catch((ex) => {
console.error('Error fetching users', ex);
}).finally(() => {
this.userLoaded = true;
};
Assuming I have to do same thing in angular 2
How to add finally block in angular 2 promise , as of now there are only then & catch blocks available in angular 2. If not finally then is there any way to add cleanup code after execution of each method , where do i write code to do finally block activities ?
The easiest way to do this is to use the promise.finally shim.
Add it with npm install --save promise.prototype.finally
Add the typings: npm install --save-dev #types/promise.prototype.finally
In your main class, before bootstrapping the application, add the following code:
import { shim } from 'promise.prototype.finally';
shim();
You should now be able to use finally on your promises.
This is usually done using Promise.always. This takes one function, and adds a new .then on the promise that gives the same function for both success and fail states. If the function is not available in the given promise-based environment, it's pretty easy to polyfill in.
Promise.always = function(p, fn) {
return p.then(fn, fn);
}
usage:
var prom = fetch('/api/users').then...
Promise.always(prom, () => {
this.userLoaded = true;
});
return prom;
Modern solution in 2019-2020
First of all, you should avoid manually adding polyfills without a good reason for it, don't do it blindly because you've read it somewhere.
The problem you encounter has two sides: typing declarations and the implementation.
In order to use Promise.finally in your code without typing errors you should add es2018.promise to the lib option in your tsconfig.json file.
For modern projects you should use the following configuration (which is default in Angular 8):
{
"compilerOptions": {
…
"lib": [
"es2018",
"dom"
]
}
}
This will fix the typing errors you have in the editor.
Also, according to the docs and my observations the correct polyfill for Promise.finally will be added by the Angular compiler automatically, you don't have to install or add anything.
But, in general, you could add a polyfill (only if it is proven to be required) in ./src/polyfills.ts file using core-js library. Always try to use core-js instead of other libraries, cause it's an industry standard and it's used by Angular internally.
For example, Promise.finally polyfill could be added like this:
import 'core-js/features/promise/finally';
See more at core-js documentation.
Also, make sure to read browser-support and differential-loading articles of Angular documentation.

NodeJS and Javascript (requirejs) dependency injection

I am currently using requirejs to manage module js/css dependencies.
I'd like to discover the possibilities of having node do this via a centralized config file.
So instead of manually doing something like
define([
'jquery'
'lib/somelib'
'views/someview']
within each module.
I'd have node inject the dependencies ie
require('moduleA').setDeps('jquery','lib/somelib','views/someview')
Anyway, I'm interested in any projects looking at dependency injection for node.
thanks
I've come up with a solution for dependency injection. It's called injectr, and it uses node's vm library and replaces the default functionality of require when including a file.
So in your tests, instead of require('libToTest'), use injectr('libToTest' { 'libToMock' : myMock });. I wanted to make the interface as straightforward as possible, with no need to alter the code being tested. I think it works quite well.
It's just worth noting that injectr files are relative to the working directory, unlike require which is relative to the current file, but that shouldn't matter because it's only used in tests.
I've previously toyed with the idea of providing an alternate require to make a form of dependency injection available in Node.js.
Module code
For example, suppose you have following statements in code.js:
fs = require('fs');
console.log(fs.readFileSync('text.txt', 'utf-8'));
If you run this code with node code.js, then it will print out the contents of text.txt.
Injector code
However, suppose you have a test module that wants to abstract away the file system.
Your test file test.js could then look like this:
var origRequire = global.require;
global.require = dependencyLookup;
require('./code.js');
function dependencyLookup (file) {
switch (file) {
case 'fs': return { readFileSync: function () { return "test contents"; } };
default: return origRequire(file);
}
}
If you now run node test.js, it will print out "test contents", even though it includes code.js.
I've also written a module to accomplish this, it's called rewire. Just use npm install rewire and then:
var rewire = require("rewire"),
myModule = rewire("./path/to/myModule.js"); // exactly like require()
// Your module will now export a special setter and getter for private variables.
myModule.__set__("myPrivateVar", 123);
myModule.__get__("myPrivateVar"); // = 123
// This allows you to mock almost everything within the module e.g. the fs-module.
// Just pass the variable name as first parameter and your mock as second.
myModule.__set__("fs", {
readFile: function (path, encoding, cb) {
cb(null, "Success!");
}
});
myModule.readSomethingFromFileSystem(function (err, data) {
console.log(data); // = Success!
});
I've been inspired by Nathan MacInnes's injectr but used a different approach. I don't use vm to eval the test-module, in fact I use node's own require. This way your module behaves exactly like using require() (except your modifications). Also debugging is fully supported.

Categories