Error not caught in async/await Node - node.js

when I run this function in Node (compiled by Babel):
(async function test2(){
let pr = await new Promise(function (resolve, reject){
setTimeout(()=>reject(new Error('reason')))
});
try {
await pr;
} catch (e) {
console.log(e);
}
})();
You can run code in Babel REPL. Both in Babel REPL and in Node my error is not caught (you can see that in browser dev tools in Babel REPL). When I start listening for unhandled rejections in Node (process.on('unhandledRejection'...) I got at least some notification:
unhandled at Promise {
_c: [],
_a: [],
_s: 2,
_d: true,
_v: [Error: reason],
_h: 0,
_n: false } because of [Error: reason]
I would like to catch those errors from try/catch - or any other way except process.on('unhandledRejection' , ...). How can I do that?

Related

Proper way to handle fetch errors in NodeJS v18?

I switched to NodeJS v18 with the built-in fetch and I'm using it as such:
async function get511AK() {
let res = await fetch(URL, { method: 'GET' })
if (res.ok && (res.headers.get('content-type').includes('json'))) {
let data = await res.json();
jsonresponseAK = data;
} else {
console.log("(" + res.url + ') is not json');
}
}
However, sometimes I'm getting a timeout on the URL, which is going to happen, but it's causing the script to exit. I've tried wrapping this in try/catch and it did not prevent it from exiting.
This never happened in Node v12 under the node-fetch library. What else can I add to control those connection timeouts?
node:internal/deps/undici/undici:11118
Error.captureStackTrace(err, this);
^
TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11118:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Timeout.get511AK [as _onTimeout] (/home/wazebot/dot-scripts/script-relay.js:76:12) {
cause: ConnectTimeoutError: Connect Timeout Error
at onConnectTimeout (node:internal/deps/undici/undici:6625:28)
at node:internal/deps/undici/undici:6583:50
at Immediate._onImmediate (node:internal/deps/undici/undici:6614:13)
at process.processImmediate (node:internal/timers:471:21) {
code: 'UND_ERR_CONNECT_TIMEOUT'
}
}
Node.js v18.12.1
Hope it helped!
process.on('uncaughtException', console.log);
// Uncaught Exception thrown - when you throw an error and did not catch anywhere.
process.on('unhandledRejection', console.log);
// Unhandled Rejection at Promise - similar, when you fail to catch a Promise.reject.

Why does delaying handling of promise rejections sometimes result in UnhandledPromiseRejectionWarning?

When I run the following code, why do I get unhandled promise rejection warning?
async function load() {
throw new Error('error');
}
async function main() {
const promise = load();
await new Promise(resolve => setTimeout(resolve, 5000));
try {
await promise;
} catch (e) {
console.log('caught error', e);
}
}
main();
This is the output:
jkim#dev-jkim test $ node index.js
(node:25276) UnhandledPromiseRejectionWarning: Error: error
Since await promise is around a try-catch, I'm confused why my try-catch isn't catching the error. I guess it's something to do with the setTimeout since the following code works:
async function load() {
throw new Error('error');
}
async function main() {
const promise = load();
try {
await promise;
} catch (e) {
console.log('caught error', e);
}
}
main();
jkim#dev-jkim test $ node index.js
caught error Error: error
What is going on here? If promise rejections are not handled by the end of the current tick, does it automatically result in a unhandled promise rejection warning?
(I'm on node v10.16.3)
If promise rejections are not handled by the end of the current tick, does it automatically result in a unhandled promise rejection warning?
Yes. A Promise must have a rejection handler attached to it at the moment it rejects, or the rejection will count as unhandled. If you attach the rejection handler later, such as after a
await new Promise(resolve => setTimeout(resolve, 5000));
the load Promise has rejected by the time the interpreter gets to the
try {
await promise;
} catch (e) {
so, although the rejection can be caught with .catch, it wasn't caught by anything at the moment of rejection, resulting in the warning.
Best practice for this sort of thing is to always attach a rejection handler immediately - whether that means .catch, or inside a try/catch, or a Promise.all, or returning the Promise for the caller to handle.
The function load() throws an Error. When an error is thrown while a Promise is being handled, the Promise is rejected. Now, if the Error load() threw is not caught then, a UnhandledPromiseRejectionWarning is thrown by JS
A better Illustration of your code is:
function load() {
console.log("hello after some time");
}
const foo = new Promise((resolve, reject) => {
setTimeout(resolve, 5000);
})
.then(() => { load(); })
.catch((e) => { console.log(`Caught Error: ${e}`)});

Node.js puppeteer errors DeprecationWarning and UnhandledPromiseRejectionWarning

I'm new to using node.js and puppeteer and i keep getting three errors and i can't seem to work out why i'm getting the errors or how i can fix them.
I'm trying to scrape the same website over and over again but sometimes end up getting the errors.
Error one:
UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'close' of undefined
Error two:
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
Error three:
DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
My code:
const puppeteer = require("puppeteer");
const referers = require('./models/referers.json');
const agents = require('./models/agents.json');
const log = console.log;
const waitForDelay = (time) => {
try {
return new Promise((resolve) => {
setTimeout(resolve, time);
});
} catch (error) {
log('delay error', error);
}
};
const runPuppeteer = async() => {
let browser, page;
try {
log('started puppeteer');
const randomAgent = agents[Math.floor(Math.random() * agents.length)];
const randomReferer = referers[Math.floor(Math.random() * referers.length)];
log('randomAgent', randomAgent, '\n randomReferer', randomReferer);
browser = await puppeteer.launch({
headless: true,
ignoreHTTPSErrors: true,
slowMo: 10,
args: [
"--no-sandbox",
"--disable-setuid-sandbox"
]
});
page = await browser.newPage();
await page.setUserAgent(randomAgent);
await page.setJavaScriptEnabled(true);
await page.setExtraHTTPHeaders({ referer: randomReferer, 'Accept-Language': 'en' });
await page.setViewport({ width: 1680, height: 1050 });
await page.goto('https://SiteUrl.com', { waitUntil: 'domcontentloaded', timeout: 60000 });
await page.waitForSelector('.mwButton', { visible: true, timeout: 60000 });
const buttonElement = await page.$('.mwButton');
if (await buttonElement.isIntersectingViewport()) {
await page.click(".mwButton");
log('button clicked');
log(await page.title());
await waitForDelay(10000);
await page.screenshot({ path: 'screenshot.png' });
} else {
log('button not clicked');
}
} catch(error) {
log(error.message);
} finally {
await browser.close();
log('closing browser');
await setTimeout(runPuppeteer, 10000);
}
};
Those are very common and generic errors node throws when you have an exception on an async function.
The problem in your code is the finally clause, it attempts to close the browser, but when your code fails to create a browser object, it remains undefined when it reaches finally.
You could do if(browser) await browser.close() to make sure it exists, if not there is no point in trying to close it...

Mock fetch in Jest tests throwing “invalid json response” and “Unhandled promise rejection” errors

I’m trying to do unit tests for some of my redux-saga generators and am getting a couple of errors I’m not sure how to resolve. I’m using create-react-app, so my testing suite is Jest and Enzyme.
A basic example:
Setup: src/setupTests.js
import 'jest-enzyme'
import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
global.fetch = require('jest-fetch-mock')
configure({
adapter: new Adapter(),
automock: false,
collectCoverageFrom: [
'<rootDir>/src/**/*.js',
'!<rootDir>/src/**/*.stories.js',
'!<rootDir>/node_modules/',
'!<rootDir>/src/index.js',
],
coverageThreshold: {
global: {
branches: 90,
functions: 90,
lines: 90,
statements: 90,
},
},
verbose: true,
})
Saga: src/store/sagas/api-saga.js
import { takeEvery, put } from 'redux-saga/effects'
import {
API_ERRORED,
DATA_LOADED,
DATA_REQUESTED,
} from '../constants/action-types'
export function* workerSaga() {
try {
const payload =
yield fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
yield put({
type: DATA_LOADED,
payload,
})
} catch (e) {
yield put({
type: API_ERRORED,
payload: false,
})
}
}
export default function* watcherSaga() {
yield takeEvery(
DATA_REQUESTED,
workerSaga,
)
}
Saga Test: src/store/sagas/api-saga.test.js
import { put, takeEvery } from 'redux-saga/effects'
import watcherSaga, { workerSaga } from './api-saga'
import {
API_ERRORED,
DATA_LOADED,
DATA_REQUESTED,
} from '../constants/action-types'
describe('saga workers', () => {
test('should dispatch action "DATA_LOADED" with result from fetch API',
() => {
const articles = 'Some content'
const mockResponse = {
articles,
}
const generator = workerSaga()
generator.next()
expect(generator.next(mockResponse).value)
.toEqual(
put({
type: DATA_LOADED,
payload: {
articles,
},
})
)
expect(generator.next().done).toBeTruthy()
})
})
The errors I’m receiving:
(node:2009) UnhandledPromiseRejectionWarning: FetchError: invalid json response body at undefined reason: Unexpected end of JSON input
(node:2009) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:2009) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:2009) UnhandledPromiseRejectionWarning: FetchError: invalid json response body at undefined reason: Unexpected end of JSON input
(node:2009) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 4)
I thought introducing jest-fetch-mock would help resolve some of these issues, but it doesn’t seem so. The tests pass, but these errors persist.
What am I missing?
Versions
redux#4.0.4
redux-saga#1.0.5
enzyme#3.10.0
jest#24.7.1
You'll need to set up jest-fetch-mock to return a value:
Something like:
describe('saga workers', () => {
test('should dispatch action "DATA_LOADED" with result from fetch API',
() => {
const articles = 'Some content'
const mockResponse = {
articles,
}
// configure the mockResponse here:
fetch.mockResponse(mockResponse);
const generator = workerSaga()
generator.next()
expect(generator.next().value)
.toEqual(
put({
type: DATA_LOADED,
payload: {
articles,
},
})
)
expect(generator.next().done).toBeTruthy()
})
})
See https://github.com/jefflau/jest-fetch-mock#api

How to test a function that throws an error asynchronously, using tape?

I am attempting to test this module (receiver.js) for an error thrown:
var request = require('request')
module.exports = function(url){
request({
url: url,
method: 'POST'
}, function(error) {
if(error){
throw error
}
})
}
using this test (test.js):
var test = require('tape')
test('Receiver test', function(t){
var receiver = require('./receiver')
t.throws(function(){
receiver('http://localhost:9999') // dummy url
}, Error, 'Should throw error with invalid URL')
t.end()
})
but tape runs the assertion before the error is thrown, resulting in the following error message:
TAP version 13
# Receiver test
not ok 1 Should throw error with invalid URL
---
operator: throws
expected: |-
[Function: Error]
actual: |-
undefined
at: Test.<anonymous> (/path/to/tape-async-error-test/test.js:5:4)
...
/path/to/receiver.js:9
throw error
^
Error: connect ECONNREFUSED 127.0.0.1:9999
at Object.exports._errnoException (util.js:856:11)
at exports._exceptionWithHostPort (util.js:879:20)
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1062:14)
Is there a way around this?
Generally, using tape, you have to ensure you call assert.end() after the async call has completed. Using promises (would require request-promise and returning the promise):
test('Receiver test', function(t){
// Tells tape to expec a single assertion
t.plan(1);
receiver('http://localhost:9999')
.then(() => {
t.fail('request should not succeed')
})
.catch(err => {
t.ok(err, 'Got expected error');
})
.finally({
t.end();
});
});
Using async/await:
test('Receiver test', async function(t) {
try {
await receiver('http://localhost:9999');
assert.fail('Should not get here');
} catch (err) {
assert.ok(err, 'Got expected error');
}
t.end();
});
The above example is mostly correct but here's a complete working example that compares async to synchronous side by side and also shows how to check for the error message in a manner similar to the tape examples given on tape's README.md.
test('ensure async function can be tested to throw', async function(t) {
// t.throw works synchronously
function normalThrower() {
throw(new Error('an artificial synchronous error'));
};
t.throws(function () { normalThrower() }, /artificial/, 'should be able to test that a normal function throws an artificial error');
// you have to do this for async functions, you can't just insert async into t.throws
async function asyncThrower() {
throw(new Error('an artificial asynchronous error'));
};
try {
await asyncThrower();
t.fail('async thrower did not throw');
} catch (e) {
t.match(e.message,/asynchronous/, 'asynchronous error was thrown');
};
});

Resources