├── preclass ├── text.txt ├── 02-promise-object.js ├── 01-tracing-events.js ├── 03-http-requests-promises.js ├── annotations.txt └── 04-mem-leak.js ├── recorded ├── text.txt ├── .vscode │ └── settings.json ├── 02-promise-object.js ├── 01-tracing-events.js ├── 05-callbacls+promises.js ├── 03-http-requests-promises.js └── 04-memory-leak.js ├── README.md └── .gitignore /preclass/text.txt: -------------------------------------------------------------------------------- 1 | hello world 2 | erick 3 | -------------------------------------------------------------------------------- /recorded/text.txt: -------------------------------------------------------------------------------- 1 | hello world 2 | erick 3 | -------------------------------------------------------------------------------- /recorded/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "window.zoomLevel": 3 3 | } -------------------------------------------------------------------------------- /preclass/02-promise-object.js: -------------------------------------------------------------------------------- 1 | new Promise(async (resolve, reject) => { 2 | // throw new Error('errr') 3 | await Promise.reject('oh sh*t') 4 | // reject('oh!') 5 | }).catch(err => console.error(err)) -------------------------------------------------------------------------------- /recorded/02-promise-object.js: -------------------------------------------------------------------------------- 1 | new Promise(async (resolve, reject)=> { 2 | // await Promise.reject('oh sh*t') 3 | // throw new Error('oh no!') 4 | // resolve('ok') 5 | reject('OH no!') 6 | }) 7 | .then(result => console.log('result', result)) 8 | .catch(error => console.log('error**', error)) -------------------------------------------------------------------------------- /preclass/01-tracing-events.js: -------------------------------------------------------------------------------- 1 | // node --trace-event-categories v8,node,node.async_hooks 02-tracing-events.js 2 | 3 | let counter = 0; 4 | setInterval(() => { 5 | // compare tracing with and without for below 6 | for (var n = 0; n < 1e9; n++) {} 7 | if (counter++ >= 10) process.exit() 8 | console.log('test') 9 | }, 500); -------------------------------------------------------------------------------- /recorded/01-tracing-events.js: -------------------------------------------------------------------------------- 1 | // node --trace-events-enabled 01-tracing-events.js 2 | // node --trace-event-categories v8,node,node.async_hooks 01-tracing-events.js 3 | 4 | let counter = 0; 5 | setInterval(() => { 6 | // for (let index = 0; index < 1e9; index++); 7 | if (counter++ >= 10) process.exit() 8 | console.log('test...') 9 | }) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Broken Promises - Learning from tech talks 2 | 3 | ## About 4 | Welcome, this repo is part of my [**youtube video**](https://youtu.be/z99xPOyaNnQ) about Broken Promises - Everything you should NOT do while using JavaScript Promises - #LearningfromTalks 5 | First of all, leave your star 🌟 on this repo. 6 | 7 | Access our [**exclusive telegram channel**](https://bit.ly/canalerickwendel) so I'll let you know about all the content I've been producing 8 | 9 | ## Complete source code 10 | - Access it in [app](./recorded/) 11 | 12 | 13 | ![Broken Promises - Everything you should NOT do while using JavaScript Promises - #LearningfromTalks](https://user-images.githubusercontent.com/8060102/196412497-4f9ca94f-dbdc-42a2-ba35-db0b097977b6.jpg) 14 | 15 | ## Have fun! 16 | 17 | -------------------------------------------------------------------------------- /recorded/05-callbacls+promises.js: -------------------------------------------------------------------------------- 1 | // node --trace-events-enabled 03-http-requests-promises.js 2 | // autocannon -d 2 localhost:3000 3 | const { 4 | createServer 5 | } = require('node:http') 6 | 7 | const { 8 | setTimeout 9 | } = require('node:timers/promises') 10 | 11 | async function insertData() { 12 | await setTimeout(1000); 13 | await Promise.reject('oh no!') 14 | return 'ok' 15 | } 16 | 17 | 18 | function handle (request, response) { 19 | return (async (request, response) => { 20 | const result = await insertData() 21 | response.end(result) 22 | })() 23 | .catch(error => { 24 | console.error('error**', error) 25 | 26 | response.writeHead(500) 27 | response.end('error!') 28 | }) 29 | } 30 | 31 | createServer(handle) 32 | .listen(3000) 33 | .on('listening', () => console.log('running at 3000')) -------------------------------------------------------------------------------- /recorded/03-http-requests-promises.js: -------------------------------------------------------------------------------- 1 | // node --trace-events-enabled 03-http-requests-promises.js 2 | // autocannon -d 2 localhost:3000 3 | const { 4 | createServer 5 | } = require('node:http') 6 | 7 | // ; 8 | // (async () => { 9 | // Promise.resolve("abc") 10 | // .then(result => { 11 | // for (let index = 0; index < 1e9; index++); 12 | // Promise.resolve("abc").then(_ => { 13 | // for (let index = 0; index < 1e9; index++); 14 | // process.exit(); 15 | // }) 16 | // }) 17 | // })(); 18 | 19 | async function perfom() { 20 | return 'abc' 21 | } 22 | 23 | createServer(async (request, response) => { 24 | await perfom() 25 | // perfom().then(() => { 26 | // response.end('hello!') 27 | // process.exit() 28 | // }) 29 | 30 | response.end('hello!') 31 | }) 32 | .listen(3000) 33 | .on('listening', () => console.log('running at 3000')) -------------------------------------------------------------------------------- /preclass/03-http-requests-promises.js: -------------------------------------------------------------------------------- 1 | // node --trace-events-enabled 03-http-requests-promises.js 2 | // node --trace-event-categories v8,node,node.async_hooks,node.perf 03-http-requests-promises.js 3 | // autocannon localhost:3000 -d 2 4 | 5 | /** 6 | * create an example with 7 | * createServer 8 | * createServer + async 9 | * createServer + await perform 10 | * createServer + perform.then 11 | */ 12 | const { 13 | createServer 14 | } = require('node:http') 15 | 16 | async function perform() { 17 | return 'abc' 18 | } 19 | 20 | // ;(async () => { 21 | // Promise.resolve("abc").then(() => { 22 | // for (let index = 0; index < 1e9; index++); 23 | // Promise.resolve("abc").then(() => { 24 | // for (let index = 0; index < 1e9; index++); 25 | // process.exit() 26 | // }) 27 | // }) 28 | // })(); 29 | 30 | createServer((request, response) => { 31 | // const result = await perform() 32 | // response.end(result) 33 | 34 | // perform().then(result => { 35 | // response.end(result) 36 | // }) 37 | 38 | response.end('result') 39 | // process.exit(0) 40 | }) 41 | .listen(3000) 42 | .on('listening', () => console.log('running at 3000')) 43 | 44 | // https://stackoverflow.com/a/54099780/4087199 -------------------------------------------------------------------------------- /preclass/annotations.txt: -------------------------------------------------------------------------------- 1 | single request reaching 30K promises 2 | 3 | - Promises are both powerful abstraction and exceeding easy to do incorrectly 4 | - Understanding how JS and Node scheudle code is key to understanding Node.js perf 5 | 6 | - FileRead executed in a thread pool 7 | read a file in a separed thread 8 | v8 turns 9 | when the data is available 10 | v8 start executing JS again 11 | With a promise you never ever want to create a promise that resolves at the same execution block 12 | - avoid Promise.resolve 13 | You wanna use and and execute excplicity actuvitiy in the future 14 | - end up with orphan promise chains with no way to handle it 15 | - memory leaks 16 | setImmediate(async () => { 17 | await Promise.reject('immediate**') 18 | }) 19 | - An empty async function with nothing in it allocates 3 Promises 20 | - never wrap a sync function with async keyword 21 | 22 | https://www.jasnell.me/posts/broken-promises 23 | https://www.nearform.com/blog/optimise-node-js-performance-avoiding-broken-promises/ 24 | 25 | Put just an async into a function 26 | - show in the chrome tracing how many promises were being creating from requests 27 | chrome://tracing/ 28 | https://ui.perfetto.dev/ 29 | 30 | 31 | 32 | --- 33 | 34 | -------------------------------------------------------------------------------- /recorded/04-memory-leak.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs') 2 | const fsPromises = require('node:fs/promises') 3 | const { 4 | promisify 5 | } = require('util') 6 | const asyncOpen = promisify(fs.open.bind(fs)) 7 | const asyncClose = promisify(fs.close.bind(fs)) 8 | const asyncWrite = promisify(fs.write.bind(fs)) 9 | 10 | const filename = './text.txt' 11 | 12 | // too many open files - memory leak here because never closes the files and reaches 13 | // the limit 14 | // ;(async () => { 15 | // while(true) await asyncOpen(filename, "r+") 16 | // })() 17 | let globalFileDescriptor = 0; 18 | let leakFd = false;; 19 | (async () => { 20 | globalFileDescriptor = await asyncOpen(filename, "r+") 21 | leakFd = true 22 | 23 | // try catch will never catch those results! 24 | try { 25 | fs.write(globalFileDescriptor, 'hello world\n', async (error, result) => { 26 | g('its leaked?', leakFd) 27 | 28 | }) 29 | } catch (error) { 30 | 31 | } 32 | })(); 33 | 34 | 35 | function handleErrorCallback(error) { 36 | async function handle() { 37 | console.log('error', error.message, { 38 | leakFd 39 | }) 40 | console.log('I was able to write in a file that wasnt supposed to be opened') 41 | await asyncClose(globalFileDescriptor) 42 | 43 | await asyncWrite(globalFileDescriptor, 'againnnn\n') 44 | } 45 | 46 | handle() 47 | .catch(error => console.log('I cought it!', error)) 48 | } 49 | 50 | 51 | [ 52 | "uncaughtException", 53 | "unhandledRejection" 54 | ].map(event => process.on(event, handleErrorCallback)) -------------------------------------------------------------------------------- /preclass/04-mem-leak.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const { 3 | promisify 4 | } = require('util') 5 | 6 | // open and close are already in fs.promises but I wanted to parse it 7 | // as write is not there 8 | const [asyncOpen, asyncClose, asyncWrite] = [ 9 | fs.open, 10 | fs.close, 11 | fs.write, 12 | ].map(fn => promisify(fn.bind(fn))) 13 | 14 | const filename = './text.txt' 15 | let leakfd = false 16 | let globalFileDescriptor = 0; 17 | 18 | // too many open files, if you open and never close descriptors 19 | // ;(async () => { 20 | // while(true) await asyncOpen(filename, 'r+') 21 | // })() 22 | 23 | ; 24 | (async function main() { 25 | const fileDescriptor = await asyncOpen(filename, 'r+') 26 | globalFileDescriptor = fileDescriptor; 27 | 28 | await asyncWrite(fileDescriptor, 'hello world\n') 29 | leakfd = true 30 | // will throw an error and never close the file descriptor 31 | 32 | // couldve been surrounded by a trycatch 33 | // and use asyncClose on finally 34 | // I wont do it now to show the memory leak 35 | functionThatDoesNotExist(); 36 | await asyncClose(fileDescriptor) 37 | leakfd = false 38 | })() 39 | 40 | 41 | // if you really want to mix up callbacks and Promises 42 | // make sure you handle rejections before returning it to the caller 43 | function handleErrorCallback(err) { 44 | 45 | // process.on doesnt expect a Promise as return so we handle rejections before returning values 46 | return (async () => { 47 | 48 | console.error('Error ocurred', err.message, { 49 | leakfd 50 | }) 51 | 52 | await asyncWrite(globalFileDescriptor, 'erick\n') 53 | console.log('I was able to write in a file that wasn\'t supposed to be opened') 54 | 55 | await asyncClose(globalFileDescriptor) 56 | leakfd = false 57 | 58 | // // throws as this file descriptor is closed 59 | await asyncWrite(globalFileDescriptor, 'aew') 60 | console.log('trying again...') // never shows! 61 | })() 62 | .catch(err => console.error('catch here', err)) 63 | 64 | } 65 | 66 | ; 67 | [ 68 | 'unhandledRejection', 69 | 'uncaughtException' 70 | ] 71 | .map(ev => process.on(ev, handleErrorCallback)) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | # Logs 132 | logs 133 | *.log 134 | npm-debug.log* 135 | yarn-debug.log* 136 | yarn-error.log* 137 | lerna-debug.log* 138 | .pnpm-debug.log* 139 | 140 | # Diagnostic reports (https://nodejs.org/api/report.html) 141 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 142 | 143 | # Runtime data 144 | pids 145 | *.pid 146 | *.seed 147 | *.pid.lock 148 | 149 | # Directory for instrumented libs generated by jscoverage/JSCover 150 | lib-cov 151 | 152 | # Coverage directory used by tools like istanbul 153 | coverage 154 | *.lcov 155 | 156 | # nyc test coverage 157 | .nyc_output 158 | 159 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 160 | .grunt 161 | 162 | # Bower dependency directory (https://bower.io/) 163 | bower_components 164 | 165 | # node-waf configuration 166 | .lock-wscript 167 | 168 | # Compiled binary addons (https://nodejs.org/api/addons.html) 169 | build/Release 170 | 171 | # Dependency directories 172 | node_modules/ 173 | jspm_packages/ 174 | 175 | # Snowpack dependency directory (https://snowpack.dev/) 176 | web_modules/ 177 | 178 | # TypeScript cache 179 | *.tsbuildinfo 180 | 181 | # Optional npm cache directory 182 | .npm 183 | 184 | # Optional eslint cache 185 | .eslintcache 186 | 187 | # Optional stylelint cache 188 | .stylelintcache 189 | 190 | # Microbundle cache 191 | .rpt2_cache/ 192 | .rts2_cache_cjs/ 193 | .rts2_cache_es/ 194 | .rts2_cache_umd/ 195 | 196 | # Optional REPL history 197 | .node_repl_history 198 | 199 | # Output of 'npm pack' 200 | *.tgz 201 | 202 | # Yarn Integrity file 203 | .yarn-integrity 204 | 205 | # dotenv environment variable files 206 | .env 207 | .env.development.local 208 | .env.test.local 209 | .env.production.local 210 | .env.local 211 | 212 | # parcel-bundler cache (https://parceljs.org/) 213 | .cache 214 | .parcel-cache 215 | 216 | # Next.js build output 217 | .next 218 | out 219 | 220 | # Nuxt.js build / generate output 221 | .nuxt 222 | dist 223 | 224 | # Gatsby files 225 | .cache/ 226 | # Comment in the public line in if your project uses Gatsby and not Next.js 227 | # https://nextjs.org/blog/next-9-1#public-directory-support 228 | # public 229 | 230 | # vuepress build output 231 | .vuepress/dist 232 | 233 | # vuepress v2.x temp and cache directory 234 | .temp 235 | .cache 236 | 237 | # Docusaurus cache and generated files 238 | .docusaurus 239 | 240 | # Serverless directories 241 | .serverless/ 242 | 243 | # FuseBox cache 244 | .fusebox/ 245 | 246 | # DynamoDB Local files 247 | .dynamodb/ 248 | 249 | # TernJS port file 250 | .tern-port 251 | 252 | # Stores VSCode versions used for testing VSCode extensions 253 | .vscode-test 254 | 255 | # yarn v2 256 | .yarn/cache 257 | .yarn/unplugged 258 | .yarn/build-state.yml 259 | .yarn/install-state.gz 260 | .pnp.* 261 | --------------------------------------------------------------------------------