diff --git a/sections/errorhandling/asyncerrorhandling.md b/sections/errorhandling/asyncerrorhandling.md index 4fa9653c..795dcea3 100644 --- a/sections/errorhandling/asyncerrorhandling.md +++ b/sections/errorhandling/asyncerrorhandling.md @@ -8,13 +8,14 @@ Callbacks don’t scale well since most programmers are not familiar with them. ```javascript return functionA() - .then((valueA) => functionB(valueA)) - .then((valueB) => functionC(valueB)) - .then((valueC) => functionD(valueC)) + .then(functionB) + .then(functionC) + .then(functionD) .catch((err) => logger.error(err)) - .then(alwaysExecuteThisFunction()) + .then(alwaysExecuteThisFunction) ``` + ### Code Example - using async/await to catch errors ```javascript @@ -25,7 +26,7 @@ async function executeAsyncTask () { const valueC = await functionC(valueB); return await functionD(valueC); } - catch(err) { + catch (err) { logger.error(err); } finally { await alwaysExecuteThisFunction(); @@ -35,6 +36,9 @@ async function executeAsyncTask () { ### Anti pattern code example – callback style error handling +
+Javascript + ```javascript getData(someParameter, function(err, result) { if(err !== null) { @@ -45,7 +49,7 @@ getData(someParameter, function(err, result) { getMoreData(b, function(c) { getMoreData(d, function(e) { if(err !== null ) { - // you get the idea?  + // you get the idea? } }) }); @@ -54,6 +58,31 @@ getData(someParameter, function(err, result) { } }); ``` +
+ +
+Typescript + +```typescript +getData(someParameter, function(err: Error | null, resultA: ResultA) { + if(err !== null) { + // do something like calling the given callback function and pass the error + getMoreData(resultA, function(err: Error | null, resultB: ResultB) { + if(err !== null) { + // do something like calling the given callback function and pass the error + getMoreData(resultB, function(resultC: ResultC) { + getMoreData(resultC, function(err: Error | null, d: ResultD) { + if(err !== null) { + // you get the idea? + } + }) + }); + } + }); + } +}); +``` +
### Blog Quote: "We have a problem with promises" diff --git a/sections/errorhandling/catchunhandledpromiserejection.md b/sections/errorhandling/catchunhandledpromiserejection.md index b2e890d0..540aeee5 100644 --- a/sections/errorhandling/catchunhandledpromiserejection.md +++ b/sections/errorhandling/catchunhandledpromiserejection.md @@ -4,7 +4,7 @@ ### One Paragraph Explainer -Typically, most of modern Node.js/Express application code runs within promises – whether within the .then handler, a function callback or in a catch block. Surprisingly, unless a developer remembered to add a .catch clause, errors thrown at these places are not handled by the uncaughtException event-handler and disappear. Recent versions of Node added a warning message when an unhandled rejection pops, though this might help to notice when things go wrong but it's obviously not a proper error handling method. The straightforward solution is to never forget adding .catch clauses within each promise chain call and redirect to a centralized error handler. However, building your error handling strategy only on developer’s discipline is somewhat fragile. Consequently, it’s highly recommended using a graceful fallback and subscribe to `process.on(‘unhandledRejection’, callback)` – this will ensure that any promise error, if not handled locally, will get its treatment. +Typically, most of modern Node.js/Express application code runs within promises – whether within the .then handler, a function callback or in a catch block. Surprisingly, unless a developer remembered to add a .catch clause, errors thrown at these places are not handled by the uncaughtException event-handler and disappear. Recent versions of Node added a warning message when an unhandled rejection pops, though this might help to notice when things go wrong but it's obviously not a proper error handling method. The straightforward solution is to never forget adding .catch clauses within each promise chain call and redirect to a centralized error handler. However, building your error handling strategy only on developer’s discipline is somewhat fragile. Consequently, it’s highly recommended using a graceful fallback and subscribe to `process.on('unhandledRejection', callback)` – this will ensure that any promise error, if not handled locally, will get its treatment.

@@ -13,29 +13,54 @@ Typically, most of modern Node.js/Express application code runs within promises ```javascript DAL.getUserById(1).then((johnSnow) => { // this error will just vanish - if(johnSnow.isAlive == false) + if(johnSnow.isAlive === false) throw new Error('ahhhh'); }); - ```

### Code example: Catching unresolved and rejected promises +
+Javascript + ```javascript process.on('unhandledRejection', (reason, p) => { - // I just caught an unhandled promise rejection, since we already have fallback handler for unhandled errors (see below), let throw and let him handle that + // I just caught an unhandled promise rejection, + // since we already have fallback handler for unhandled errors (see below), + // let throw and let him handle that throw reason; }); + process.on('uncaughtException', (error) => { // I just received an error that was never handled, time to handle it and then decide whether a restart is needed errorManagement.handler.handleError(error); if (!errorManagement.handler.isTrustedError(error)) process.exit(1); }); - ``` +
+ +
+Typescript + +```typescript +process.on('unhandledRejection', (reason: string, p: Promise) => { + // I just caught an unhandled promise rejection, + // since we already have fallback handler for unhandled errors (see below), + // let throw and let him handle that + throw reason; +}); + +process.on('uncaughtException', (error: Error) => { + // I just received an error that was never handled, time to handle it and then decide whether a restart is needed + errorManagement.handler.handleError(error); + if (!errorManagement.handler.isTrustedError(error)) + process.exit(1); +}); +``` +


@@ -46,16 +71,16 @@ process.on('uncaughtException', (error) => { > Let’s test your understanding. Which of the following would you expect to print an error to the console? ```javascript -Promise.resolve(‘promised value’).then(() => { - throw new Error(‘error’); +Promise.resolve('promised value').then(() => { + throw new Error('error'); }); -Promise.reject(‘error value’).catch(() => { - throw new Error(‘error’); +Promise.reject('error value').catch(() => { + throw new Error('error'); }); new Promise((resolve, reject) => { - throw new Error(‘error’); + throw new Error('error'); }); ``` diff --git a/sections/errorhandling/centralizedhandling.md b/sections/errorhandling/centralizedhandling.md index 097a9922..53d7bf62 100644 --- a/sections/errorhandling/centralizedhandling.md +++ b/sections/errorhandling/centralizedhandling.md @@ -6,6 +6,9 @@ Without one dedicated object for error handling, greater are the chances of impo ### Code Example – a typical error flow +
+Javascript + ```javascript // DAL layer, we don't handle errors here DB.addDocument(newCustomer, (error, result) => { @@ -33,9 +36,46 @@ app.use(async (err, req, res, next) => { } }); ``` +
+ +
+Typescript + +```typescript +// DAL layer, we don't handle errors here +DB.addDocument(newCustomer, (error: Error, result: Result) => { + if (error) + throw new Error("Great error explanation comes here", other useful parameters) +}); + +// API route code, we catch both sync and async errors and forward to the middleware +try { + customerService.addNew(req.body).then((result: Result) => { + res.status(200).json(result); + }).catch((error: Error) => { + next(error) + }); +} +catch (error) { + next(error); +} + +// Error handling middleware, we delegate the handling to the centralized error handler +app.use(async (err: Error, req: Request, res: Response, next: NextFunction) => { + const isOperationalError = await errorHandler.handleError(err); + if (!isOperationalError) { + next(err); + } +}); +``` +
+ ### Code example – handling errors within a dedicated object +
+Javascript + ```javascript module.exports.handler = new errorHandler(); @@ -48,9 +88,31 @@ function errorHandler() { }; } ``` +
+ +
+Typescript + +```typescript +class ErrorHandler { + public async handleError(err: Error): Promise { + await logger.logError(err); + await sendMailToAdminIfCritical(); + await saveInOpsQueueIfCritical(); + await determineIfOperationalError(); + }; +} + +export const handler = new ErrorHandler(); +``` +
+ ### Code Example – Anti Pattern: handling errors within the middleware +
+Javascript + ```javascript // middleware handling the error directly, who will handle Cron jobs and testing errors? app.use((err, req, res, next) => { @@ -63,6 +125,25 @@ app.use((err, req, res, next) => { } }); ``` +
+ + +
+Typescript + +```typescript +// middleware handling the error directly, who will handle Cron jobs and testing errors? +app.use((err: Error, req: Request, res: Response, next: NextFunction) => { + logger.logError(err); + if (err.severity == errors.high) { + mailer.sendMail(configuration.adminMail, 'Critical error occured', err); + } + if (!err.isOperational) { + next(err); + } +}); +``` +
### Blog Quote: "Sometimes lower levels can’t do anything useful except propagate the error to their caller" diff --git a/sections/errorhandling/failfast.md b/sections/errorhandling/failfast.md index b2f89b02..805d7ac9 100644 --- a/sections/errorhandling/failfast.md +++ b/sections/errorhandling/failfast.md @@ -22,11 +22,15 @@ function addNewMember(newMember) { Joi.assert(newMember, memberSchema); //throws if validation fails // other logic here } - ``` + + ### Anti-pattern: no validation yields nasty bugs +
+Javascript + ```javascript // if the discount is positive let's then redirect the user to print his discount coupons function redirectToPrintDiscount(httpResponse, member, discount) { @@ -37,8 +41,24 @@ function redirectToPrintDiscount(httpResponse, member, discount) { redirectToPrintDiscount(httpResponse, someMember); // forgot to pass the parameter discount, why the heck was the user redirected to the discount screen? - ``` +
+ +
+Typescript + +```typescript +// if the discount is positive let's then redirect the user to print his discount coupons +function redirectToPrintDiscount(httpResponse: Response, member: Member, discount: number) { + if (discount != 0) { + httpResponse.redirect(`/discountPrintView/${member.id}`); + } +} + +redirectToPrintDiscount(httpResponse, someMember, -12); +// We passed a negative parameter discount, why the heck was the user redirected to the discount screen? +``` +
### Blog Quote: "You should throw these errors immediately" diff --git a/sections/errorhandling/operationalvsprogrammererror.md b/sections/errorhandling/operationalvsprogrammererror.md index a6175c23..fa98057b 100644 --- a/sections/errorhandling/operationalvsprogrammererror.md +++ b/sections/errorhandling/operationalvsprogrammererror.md @@ -6,6 +6,9 @@ Distinguishing the following two error types will minimize your app downtime and ### Code Example – marking an error as operational (trusted) +
+Javascript + ```javascript // marking an error object as operational const myError = new Error("How can I add new product when no value provided?"); @@ -25,6 +28,34 @@ class AppError { throw new AppError(errorManagement.commonErrors.InvalidInput, "Describe here what happened", true); ``` +
+ +
+Typescript + +```typescript +// some centralized error factory (see other examples at the bullet "Use only the built-in Error object") +export class AppError extends Error { + public readonly commonType: string; + public readonly isOperational: boolean; + + constructor(commonType: string, description: string, isOperational: boolean) { + super(description); + + Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain + + this.commonType = commonType; + this.isOperational = isOperational; + + Error.captureStackTrace(this); + } +} + +// marking an error object as operational (true) +throw new AppError(errorManagement.commonErrors.InvalidInput, "Describe here what happened", true); + +``` +
### Blog Quote: "Programmer errors are bugs in the program" diff --git a/sections/errorhandling/shuttingtheprocess.md b/sections/errorhandling/shuttingtheprocess.md index 647eadcb..9062ec48 100644 --- a/sections/errorhandling/shuttingtheprocess.md +++ b/sections/errorhandling/shuttingtheprocess.md @@ -6,12 +6,15 @@ Somewhere within your code, an error handler object is responsible for deciding ### Code example: deciding whether to crash +
+Javascript + ```javascript // Assuming developers mark known operational errors with error.isOperational=true, read best practice #3 process.on('uncaughtException', function(error) { errorManagement.handler.handleError(error); if(!errorManagement.handler.isTrustedError(error)) - process.exit(1) + process.exit(1) }); // centralized error handler encapsulates error-handling related logic @@ -28,6 +31,51 @@ function errorHandler() { } } ``` +
+ +
+Typescript + +```typescript +// Assuming developers mark known operational errors with error.isOperational=true, read best practice #3 +process.on('uncaughtException', (error: Error) => { + errorManagement.handler.handleError(error); + if(!errorManagement.handler.isTrustedError(error)) + process.exit(1) +}); + +// centralized error object that derives from Node’s Error +export class AppError extends Error { + public readonly isOperational: boolean; + + constructor(description: string, isOperational: boolean) { + super(description); + Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain + this.isOperational = isOperational; + Error.captureStackTrace(this); + } +} + +// centralized error handler encapsulates error-handling related logic +class ErrorHandler { + public async handleError(err: Error): Promise { + await logger.logError(err); + await sendMailToAdminIfCritical(); + await saveInOpsQueueIfCritical(); + await determineIfOperationalError(); + }; + + public isTrustedError(error: Error) { + if (error instanceof AppError) { + return error.isOperational; + } + return false; + } +} + +export const handler = new ErrorHandler(); +``` +
### Blog Quote: "The best way is to crash" diff --git a/sections/errorhandling/testingerrorflows.md b/sections/errorhandling/testingerrorflows.md index 86ac61e7..83908200 100644 --- a/sections/errorhandling/testingerrorflows.md +++ b/sections/errorhandling/testingerrorflows.md @@ -6,6 +6,9 @@ Testing ‘happy’ paths is no better than testing failures. Good testing code ### Code example: ensuring the right exception is thrown using Mocha & Chai +
+Javascript + ```javascript describe("Facebook chat", () => { it("Notifies on new chat message", () => { @@ -14,13 +17,30 @@ describe("Facebook chat", () => { expect(chatService.sendMessage.bind({ message: "Hi" })).to.throw(ConnectionError); }); }); - ``` +
+ +
+Typescript + +```typescript +describe("Facebook chat", () => { + it("Notifies on new chat message", () => { + const chatService = new chatService(); + chatService.participants = getDisconnectedParticipants(); + expect(chatService.sendMessage.bind({ message: "Hi" })).to.throw(ConnectionError); + }); +}); +``` +
### Code example: ensuring API returns the right HTTP error code +
+Javascript + ```javascript -it("Creates new Facebook group", function (done) { +it("Creates new Facebook group", (done) => { var invalidGroupInfo = {}; httpRequest({ method: 'POST', @@ -30,9 +50,33 @@ it("Creates new Facebook group", function (done) { json: true }).then((response) => { // if we were to execute the code in this block, no error was thrown in the operation above - }).catch(function (response) { + }).catch((response) => { expect(400).to.equal(response.statusCode); done(); }); }); ``` +
+ +
+Typescript + +```typescript +it("Creates new Facebook group", async () => { + let invalidGroupInfo = {}; + try { + const response = await httpRequest({ + method: 'POST', + uri: "facebook.com/api/groups", + resolveWithFullResponse: true, + body: invalidGroupInfo, + json: true + }) + // if we were to execute the code in this block, no error was thrown in the operation above + expect.fail('The request should have failed') + } catch(response) { + expect(400).to.equal(response.statusCode); + } +}); +``` +
\ No newline at end of file diff --git a/sections/errorhandling/usematurelogger.md b/sections/errorhandling/usematurelogger.md index 4069d04d..286f2c24 100644 --- a/sections/errorhandling/usematurelogger.md +++ b/sections/errorhandling/usematurelogger.md @@ -11,7 +11,7 @@ We all love console.log but obviously, a reputable and persistent logger like [W ```javascript // your centralized logger object -var logger = new winston.Logger({ +const logger = new winston.Logger({ level: 'info', transports: [ new (winston.transports.Console)() @@ -20,22 +20,20 @@ var logger = new winston.Logger({ // custom code somewhere using the logger logger.log('info', 'Test Log Message with some parameter %s', 'some parameter', { anything: 'This is metadata' }); - ``` ### Code Example – Querying the log folder (searching for entries) ```javascript -var options = { - from: new Date - 24 * 60 * 60 * 1000, - until: new Date, +const options = { + from: Date.now() - 24 * 60 * 60 * 1000, + until: new Date(), limit: 10, start: 0, order: 'desc', fields: ['message'] }; - // Find items logged between today and yesterday. winston.query(options, function (err, results) { // execute callback with results diff --git a/sections/errorhandling/useonlythebuiltinerror.md b/sections/errorhandling/useonlythebuiltinerror.md index 3d83c6de..8c59285a 100644 --- a/sections/errorhandling/useonlythebuiltinerror.md +++ b/sections/errorhandling/useonlythebuiltinerror.md @@ -38,6 +38,9 @@ if(!productToAdd) ### Code example – doing it even better +
+Javascript + ```javascript // centralized error object that derives from Node’s Error function AppError(name, httpCode, description, isOperational) { @@ -55,6 +58,38 @@ module.exports.AppError = AppError; if(user == null) throw new AppError(commonErrors.resourceNotFound, commonHTTPErrors.notFound, "further explanation", true) ``` +
+ +
+Typescript + +```typescript +// centralized error object that derives from Node’s Error +export class AppError extends Error { + public readonly name: string; + public readonly httpCode: HttpCode; + public readonly isOperational: boolean; + + constructor(name: string, httpCode: HttpCode, description: string, isOperational: boolean) { + super(description); + + Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain + + this.name = name; + this.httpCode = httpCode; + this.isOperational = isOperational; + + Error.captureStackTrace(this); + } +} + +// client throwing an exception +if(user == null) + throw new AppError(commonErrors.resourceNotFound, commonHTTPErrors.notFound, "further explanation", true) +``` +
+ +*Explanation about the `Object.setPrototypeOf` in Typescript: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget* ### Blog Quote: "I don’t see the value in having lots of different types" diff --git a/sections/projectstructre/separateexpress.md b/sections/projectstructre/separateexpress.md index c0f88679..07c6ea10 100644 --- a/sections/projectstructre/separateexpress.md +++ b/sections/projectstructre/separateexpress.md @@ -8,39 +8,54 @@ The latest Express generator comes with a great practice that is worth to keep -

-### Code example: API declaration, should reside in app.js +### Code example: API declaration, should reside in app.js/app.ts ```javascript -var app = express(); +const app = express(); app.use(bodyParser.json()); app.use("/api/events", events.API); app.use("/api/forms", forms); ``` -

- ### Code example: Server network declaration, should reside in /bin/www +
+Javascript + ```javascript var app = require('../app'); var http = require('http'); -/** - * Get port from environment and store in Express. - */ - +// Get port from environment and store in Express. var port = normalizePort(process.env.PORT || '3000'); app.set('port', port); -/** - * Create HTTP server. - */ - +// Create HTTP server. var server = http.createServer(app); ``` +
+ +
+Typescript + +```typescript +import app from '../app'; +import http from 'http'; + +// Get port from environment and store in Express. +const port = normalizePort(process.env.PORT || '3000'); +app.set('port', port); + +// Create HTTP server. +const server = http.createServer(app); +``` +
### Example: test your API in-process using supertest (popular testing package) +
+Javascript + ```javascript const app = express(); @@ -56,4 +71,28 @@ request(app) .end(function(err, res) { if (err) throw err; }); -```` +``` +
+ + +
+Typescript + +```typescript +const app = express(); + +app.get('/user', (req: Request, res: Response) => { + res.status(200).json({ name: 'tobi' }); +}); + +request(app) + .get('/user') + .expect('Content-Type', /json/) + .expect('Content-Length', '15') + .expect(200) + .end((err: Error) => { + if (err) throw err; + }); + +``` +
\ No newline at end of file