├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── arrays ├── empty-an-array │ └── index.js └── merge-array-alternating-values │ └── index.js ├── axios ├── download-files-with-progress │ ├── images │ │ └── .gitignore │ └── index.js └── download-files │ ├── images │ └── .gitignore │ └── index.js ├── code-styling ├── 01-callbacks.js ├── 02-promises.js ├── 03-async-await.js ├── async-or-not-async │ └── async-or-not-async.js ├── await-sync-function │ └── await-sync-function.js └── combine-callbacks-and-promises │ ├── base-command.js │ └── deploy-command.js ├── collections ├── array-compact │ └── array-compact.js ├── async-array-every │ └── async-array-every.js ├── async-array-filter │ └── async-array-filter.js ├── async-array-find │ └── async-array-find.js ├── async-array-forEach │ └── async-array-forEach.js ├── async-array-map │ └── async-array-map.js ├── async-array-mapSeries │ └── async-array-mapSeries.js └── async-array-some │ └── async-array-some.js ├── data-structures ├── implementations │ ├── linked-list │ │ ├── linked-list.js │ │ └── node.js │ ├── priority-queue │ │ └── priority-queue.js │ ├── queue │ │ └── queue.js │ ├── set │ │ └── set.js │ └── stack │ │ └── stack.js └── starter-files │ ├── linked-list.js │ ├── priority-queue.js │ ├── queue.js │ ├── set.js │ └── stack.js ├── date ├── increase-date-by-one-week.js └── tomorrows-date.js ├── error-handling └── unhandled-rejection.js ├── events └── async-event-listener │ ├── event.js │ ├── index.js │ └── listener.js ├── filesystem ├── create-an-empty-file │ ├── .gitignore │ └── index.js ├── file-created-date │ ├── content.txt │ └── index.js ├── file-last-updated-date │ ├── content.txt │ └── index.js └── write-json-object-to-file │ ├── content.txt │ ├── index.js │ └── promisified.js ├── flow-control ├── async-constructors │ ├── index.js │ └── sample-cache-driver.js ├── promise-and-callback │ ├── index.js │ └── promise-and-callback.js ├── promises-in-parallel │ ├── index.js │ └── with-errors.js ├── promises-in-sequence │ ├── index.js │ └── with-errors.js └── promises-waterfall │ ├── .gitignore │ └── index.js ├── loops └── for-of-vs-for-in │ └── for-of-vs-for-in.js ├── misc ├── custom-error │ ├── error.js │ └── index.js └── increased-memory-limit │ └── show-memory-limit.js ├── objects ├── deep-merge.js └── merge-objects.js ├── package.json ├── streaming └── async-readline │ ├── async-stringify-transform.js │ └── input.js ├── strings ├── json-stringify-with-spaces-and-line-breaks │ └── index.js └── string-replace-all-appearances │ └── index.js └── test └── data-structures ├── linked-list.js ├── priority-queue.js ├── queue.js ├── set.js └── stack.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": "standard", 7 | "parserOptions": { 8 | "ecmaVersion": 2018 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | package-lock.json 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | # IDEA (Webstorm) 41 | .idea 42 | 43 | # Visual Studio Code 44 | .vscode 45 | 46 | # bcrypt windows 47 | build 48 | src 49 | binding.gyp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2018 Future Studio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node.js tutorials 2 | This repository includes examples for the [Node.js tutorials published on Future Studio](https://futurestud.io/tutorials/tag/nodejs). Enjoy toying with all the individual examples and feel free to apply the practical approaches in your apps. 3 | 4 | 5 | 6 | ------ 7 | 8 |

The Node.js tutorials are supported by Future Studio University 🚀 9 |
10 | Join the Future Studio University and Skyrocket in Node.js 11 |

12 | 13 | ------ 14 | 15 | 16 | ## YouTube Playlist 17 | Even though there aren’t that many tutorials, find the [Node.js playlist on YouTube](https://www.youtube.com/watch?v=s6TNwLnhppk&list=PLpUMhvC6l7AMwyuEqLPvfEtKQbdD4BJ5o). 18 | 19 | 20 | --- 21 | 22 | 23 | ## Tutorials 24 | 25 | 26 | #### Axios 27 | - [Axios — Download Files in Node.js](https://futurestud.io/tutorials/download-files-images-with-axios-in-node-js) 28 | 29 | 30 | #### Mocha 31 | - [Mocha — Global Setup and Teardown (before/after)](https://futurestud.io/tutorials/mocha-global-setup-and-teardown-before-after) 32 | 33 | 34 | #### Flow Control 35 | - [Node.js — Run Async Functions/Promises in Sequence](https://futurestud.io/tutorials/node-js-run-async-functions-promises-in-sequence) 36 | 37 | 38 | #### Classes 39 | - [Node.js — Extend Multiple Classes (Multi Inheritance)](https://futurestud.io/tutorials/node-js-extend-multiple-classes-multi-inheritance) 40 | 41 | 42 | #### Collections 43 | - [Node.js — Run an Asynchronous Function in Array.map()](https://futurestud.io/tutorials/node-js-how-to-run-an-asynchronous-function-in-array-map) 44 | 45 | 46 | #### Filesystem 47 | - [Node.js — Get a File’s Last Modified/Updated Date](https://futurestud.io/tutorials/node-js-get-a-files-last-modified-updated-date) 48 | - [Node.js — Get a File’s Created Date](https://futurestud.io/tutorials/node-js-get-a-files-created-date) 49 | - [Node.js — Write a JSON Object to a File](https://futurestud.io/tutorials/node-js-write-a-json-object-to-a-file) 50 | 51 | 52 | #### Streaming 53 | - [Node.js — Filter Data in Streams](https://futurestud.io/tutorials/node-js-filter-data-in-streams) 54 | 55 | 56 | #### Code Styling 57 | - [Callback and Promise Support in your Node.js Modules](https://futurestud.io/tutorials/callback-and-promise-support-in-your-node-js-modules) 58 | 59 | 60 | #### Error Handling 61 | - [Node.js — Create Your Own Custom Error](https://futurestud.io/tutorials/node-js-create-your-custom-error) 62 | 63 | 64 | #### Strings 65 | - [Node.js — Human-Readable JSON.stringify() With Spaces and Line Breaks](https://futurestud.io/tutorials/node-js-human-readable-json-stringify-with-spaces-and-line-breaks) 66 | - [Node.js — String Replace All Appearances](https://futurestud.io/tutorials/node-js-string-replace-all-appearances) 67 | 68 | 69 | #### Misc 70 | - [Increase the Memory Limit for Your Node.js Process](https://futurestud.io/tutorials/node-js-increase-the-memory-limit-for-your-process) 71 | - [Get Number of Seconds Since Epoch in JavaScript](https://futurestud.io/tutorials/get-number-of-seconds-since-epoch-in-javascript) 72 | -------------------------------------------------------------------------------- /arrays/empty-an-array/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const array = [1, 2, 3, 4, 5] 4 | array.length = 0 5 | console.log(array.length) 6 | 7 | let array2 = [1, 2, 3, 4, 5] 8 | array2 = [] 9 | console.log(array2.length) 10 | -------------------------------------------------------------------------------- /arrays/merge-array-alternating-values/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _ = require('lodash') 4 | 5 | const first = [1, 2, 3, 4] 6 | const second = ['A', 'B', 'C', 'D'] 7 | 8 | const vanilla1 = Array.prototype.concat( 9 | ...first.map((item, index) => { 10 | return [item, second[index]] 11 | }) 12 | ) 13 | console.log(vanilla1) 14 | 15 | const vanilla2 = 16 | first 17 | .map((item, index) => { 18 | return [item, second[index]] 19 | }) 20 | .reduce((result, pair) => { 21 | return result.concat(pair) 22 | }) 23 | console.log(vanilla2) 24 | 25 | const lodash1 = _.flatten(_.zip(first, second)) 26 | console.log(lodash1) 27 | 28 | const lodash2 = 29 | _ 30 | .zip(first, second) 31 | .reduce((result, pair) => { 32 | return result.concat(pair) 33 | }) 34 | console.log(lodash2) 35 | -------------------------------------------------------------------------------- /axios/download-files-with-progress/images/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /axios/download-files-with-progress/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Fs = require('fs') 4 | const Path = require('path') 5 | const Axios = require('axios') 6 | const ProgressBar = require('progress') 7 | 8 | class DownloadWithProgressBar { 9 | constructor () { 10 | this.source = 'https://unsplash.com/photos/P6uF0I_okfk/download?force=true' 11 | this.destination = Path.resolve(`${__dirname}/images/hills.jpg`) 12 | } 13 | 14 | async start () { 15 | console.log('Connecting …') 16 | const { data, headers } = await this.startDownload() 17 | 18 | console.log('Starting download') 19 | const progressBar = this.initializeProgressBar({ totalLength: headers['content-length'] }) 20 | 21 | return new Promise((resolve, reject) => { 22 | const file = this.toFile() 23 | file.on('error', reject) 24 | file.on('finish', resolve) 25 | 26 | data.pipe(file) 27 | data.on('data', (chunk) => progressBar.tick(chunk.length)) 28 | }) 29 | } 30 | 31 | async startDownload () { 32 | return Axios({ 33 | method: 'GET', 34 | url: this.source, 35 | responseType: 'stream' 36 | }) 37 | } 38 | 39 | initializeProgressBar ({ totalLength }) { 40 | return new ProgressBar('-> downloading [:bar] :percent :etas', { 41 | width: 40, 42 | complete: '=', 43 | incomplete: ' ', 44 | renderThrottle: 1, 45 | total: parseInt(totalLength) 46 | }) 47 | } 48 | 49 | toFile () { 50 | return Fs.createWriteStream(this.destination) 51 | } 52 | } 53 | 54 | const download = new DownloadWithProgressBar() 55 | download.start() 56 | -------------------------------------------------------------------------------- /axios/download-files/images/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /axios/download-files/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Fs = require('fs') 4 | const Path = require('path') 5 | const Listr = require('listr') 6 | const Axios = require('axios') 7 | 8 | /** 9 | * Start tasks to prepare or destroy data in MongoDB 10 | * 11 | * @param {Listr} tasks Listr instance with tasks 12 | * @return {void} 13 | */ 14 | function kickoff (tasks) { 15 | tasks 16 | .run() 17 | .then(process.exit) 18 | .catch(process.exit) 19 | } 20 | 21 | /** 22 | * Entry point for the NPM "pumpitup" and "cleanup" scripts 23 | * Imports movie and TV show sample data to MongoDB 24 | */ 25 | if (process.argv) { 26 | const tasks = [ 27 | { 28 | title: 'Downloading images with axios', 29 | task: async () => { 30 | const url = 'https://unsplash.com/photos/AaEQmoufHLk/download?force=true' 31 | const path = Path.resolve(__dirname, 'images', 'code.jpg') 32 | const writer = Fs.createWriteStream(path) 33 | 34 | const response = await Axios({ 35 | url, 36 | method: 'GET', 37 | responseType: 'stream' 38 | }) 39 | 40 | response.data.pipe(writer) 41 | 42 | return new Promise((resolve, reject) => { 43 | writer.on('finish', resolve) 44 | writer.on('error', reject) 45 | }) 46 | } 47 | } 48 | ] 49 | 50 | kickoff(new Listr(tasks)) 51 | } 52 | -------------------------------------------------------------------------------- /code-styling/01-callbacks.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function grindCoffee (callback) { 4 | console.log('Beans grinded.') 5 | callback() 6 | } 7 | 8 | function brewCoffee (callback) { 9 | console.log('Coffee brewed.') 10 | callback() 11 | } 12 | 13 | function drinkCoffee (callback) { 14 | console.log('Work hard. Brew hard.') 15 | callback() 16 | } 17 | 18 | function breakfast () { 19 | grindCoffee(() => { 20 | brewCoffee(() => { 21 | drinkCoffee(() => { 22 | console.log('Fiery for the day!') 23 | }) 24 | }) 25 | }) 26 | } 27 | 28 | breakfast() 29 | -------------------------------------------------------------------------------- /code-styling/02-promises.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function grindCoffee () { 4 | console.log('Beans grinded.') 5 | return Promise.resolve() 6 | } 7 | 8 | function brewCoffee () { 9 | console.log('Coffee brewed.') 10 | return Promise.resolve() 11 | } 12 | 13 | function drinkCoffee () { 14 | console.log('Work hard. Brew hard.') 15 | return Promise.resolve() 16 | } 17 | 18 | function breakfast () { 19 | grindCoffee() 20 | .then(() => brewCoffee()) 21 | .then(() => drinkCoffee()) 22 | .then(() => console.log('Fiery for the day!')) 23 | } 24 | 25 | breakfast() 26 | -------------------------------------------------------------------------------- /code-styling/03-async-await.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | async function grindCoffee () { 4 | console.log('Beans grinded.') 5 | } 6 | 7 | async function brewCoffee () { 8 | console.log('Coffee brewed.') 9 | } 10 | 11 | async function drinkCoffee () { 12 | console.log('Work hard. Brew hard.') 13 | } 14 | 15 | async function breakfast () { 16 | // everything is top-level when using async/await 17 | // no nested callbacks to retrieve the results 18 | 19 | await grindCoffee() 20 | await brewCoffee() 21 | await drinkCoffee() 22 | console.log('Fiery for the day!') 23 | } 24 | 25 | breakfast() 26 | -------------------------------------------------------------------------------- /code-styling/async-or-not-async/async-or-not-async.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class PostController { 4 | async create () { 5 | // even though Promise.resolve() returns a Promise, 6 | // the `async` keyword clearly tells you the return value (=Promise) 7 | console.log('Creating new post.') 8 | 9 | await new Promise(resolve => { 10 | setTimeout(() => { 11 | console.log('New post created.') 12 | resolve() 13 | }, 1000) 14 | }) 15 | 16 | return Promise.resolve() 17 | } 18 | } 19 | 20 | const controller = new PostController() 21 | controller.create() 22 | -------------------------------------------------------------------------------- /code-styling/await-sync-function/await-sync-function.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class PostController { 4 | async find () { 5 | const result = await this._filter() 6 | console.log(result) 7 | } 8 | 9 | _filter () { 10 | return [ 1, 2, 3, 4, 5 ].filter(id => id % 2 === 0) 11 | } 12 | } 13 | 14 | const controller = new PostController() 15 | controller.find() 16 | -------------------------------------------------------------------------------- /code-styling/combine-callbacks-and-promises/base-command.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Command { 4 | async run (callback) { 5 | try { 6 | await this.ensureInProjectRoot() 7 | await callback() 8 | } catch (error) { 9 | this.prettyPrintError(error) 10 | process.exit(1) 11 | } 12 | } 13 | 14 | async ensureInProjectRoot () { 15 | // ensure that the command execution was 16 | // initiated from the project’s root 17 | } 18 | 19 | async prettyPrintError (error) { 20 | // create a nice-looking error output 21 | console.error(error) 22 | } 23 | } 24 | 25 | module.exports = Command 26 | -------------------------------------------------------------------------------- /code-styling/combine-callbacks-and-promises/deploy-command.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const BaseCommand = require('./base-command') 4 | 5 | class Deploy extends BaseCommand { 6 | async handle () { 7 | /** 8 | * Combine async/await with callbacks: the base command 9 | * implements a `run` method that accepts an async 10 | * callback as a parameter. 11 | * 12 | * This async callback method will then be processed 13 | * within `run`. Additionally, `run` implements 14 | * basic handling, like pretty error logging. 15 | */ 16 | await this.run(async () => { 17 | await this._deploy() 18 | }) 19 | } 20 | 21 | async _deploy () { 22 | // do the heavy lifting here 23 | } 24 | } 25 | 26 | module.exports = Deploy 27 | -------------------------------------------------------------------------------- /collections/array-compact/array-compact.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Removes all falsey values from the given `array`. 5 | * Falsey values are `null`, `undefined`, `''`, 6 | * `false`, `0`, `NaN`. 7 | * 8 | * @param {Array} array 9 | * 10 | * @returns {Array} 11 | */ 12 | function compact (array) { 13 | array = Array.isArray(array) ? array : [ array ] 14 | 15 | return array.filter(item => !!item) 16 | } 17 | 18 | function run () { 19 | const values = [ 0, null, undefined, 1, false, 2, '', 3, NaN ] 20 | 21 | console.log( 22 | compact(values) // output: [ 1, 2, 3 ] 23 | ) 24 | } 25 | 26 | run() 27 | -------------------------------------------------------------------------------- /collections/async-array-every/async-array-every.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Asynchronous version of `Array#every()` testing 5 | * whether all elements in the `array` pass the 6 | * test implemented by the `callback` function. 7 | * 8 | * @param {Array} array 9 | * @param {Function} callback 10 | * 11 | * @returns {Boolean} 12 | */ 13 | async function every (array, callback) { 14 | array = Array.isArray(array) ? array : [ array ] 15 | 16 | const values = await Promise.all(array.map(callback)) 17 | 18 | return values.every((value) => !!value) 19 | } 20 | 21 | /** 22 | * Helper function that waits for the given number of `ms`. 23 | * 24 | * @param {Integer} ms 25 | * 26 | * @returns {Integer} 27 | */ 28 | async function wait (ms) { 29 | await new Promise(resolve => setTimeout(resolve, ms)) 30 | 31 | return ms 32 | } 33 | 34 | async function run () { 35 | const timeouts = [10, 600, 200, 775, 125, 990] 36 | 37 | console.log('Is every waiting time greater than 500? ->') 38 | console.log( 39 | await every(timeouts, async timeout => { 40 | return await wait(timeout) > 500 41 | }) 42 | 43 | ) 44 | 45 | console.log('\nIs every waiting time greater than 5? ->') 46 | console.log( 47 | await every(timeouts, async timeout => { 48 | return await wait(timeout) > 5 49 | }) 50 | ) 51 | } 52 | 53 | run() 54 | -------------------------------------------------------------------------------- /collections/async-array-filter/async-array-filter.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Asynchronously filter the given `array` using the provided `callback`. 5 | * At first, this function uses an async version of Array.map to run the 6 | * `callback` on every item. This returns an array of boolean values, 7 | * like `[ true, false, true ]`. The final filter results will be 8 | * calculated based on the boolean results and only those items 9 | * having a `true` result in the boolean array will survive. 10 | * 11 | * 12 | * @param {Array} array 13 | * @param {Function} callback 14 | * 15 | * @returns {Array} 16 | */ 17 | async function filter (array, callback) { 18 | array = Array.isArray(array) ? array : [ array ] 19 | 20 | const values = await Promise.all(array.map(callback)) 21 | 22 | return array.filter((_, index) => values[index]) 23 | } 24 | 25 | /** 26 | * Helper function that waits for the given number of `ms`. 27 | * 28 | * @param {Integer} ms 29 | * 30 | * @returns {Integer} 31 | */ 32 | async function wait (ms) { 33 | await new Promise(resolve => setTimeout(resolve, ms)) 34 | 35 | return ms 36 | } 37 | 38 | async function run () { 39 | const timeouts = [10, 600, 200, 775, 125, 990] 40 | 41 | console.log( 42 | await filter(timeouts, async timeout => { 43 | return await wait(timeout) > 500 44 | }) 45 | ) 46 | } 47 | 48 | run() 49 | -------------------------------------------------------------------------------- /collections/async-array-find/async-array-find.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Asynchronous version of Array#find(). Returns the first 5 | * item in the `array` that satisfies the `callback` 6 | * testing function, `undefined` otherwise. 7 | * 8 | * @param {Array} array 9 | * @param {Function} callback 10 | * 11 | * @returns {*} the found value 12 | */ 13 | async function find (array, callback) { 14 | const mapped = await Promise.all(array.map(callback)) 15 | 16 | return array.find((_, i) => mapped[i]) 17 | } 18 | 19 | /** 20 | * Helper function that waits for the given number of `ms`. 21 | * 22 | * @param {Integer} ms 23 | * 24 | * @returns {Integer} 25 | */ 26 | async function wait (ms) { 27 | await new Promise(resolve => setTimeout(resolve, ms)) 28 | 29 | return ms 30 | } 31 | 32 | async function run () { 33 | const timeouts = [10, 600, 200, 775, 125, 990] 34 | 35 | console.log( 36 | await find(timeouts, async timeout => { 37 | return await wait(timeout) > 700 38 | }) 39 | ) 40 | } 41 | 42 | run() 43 | -------------------------------------------------------------------------------- /collections/async-array-forEach/async-array-forEach.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Asynchrounous version of Array#forEach(). It runs the given 5 | * `callback` function on each `array` item. The callback 6 | * receives the current array item as a parameter. 7 | * 8 | * @param {Array} array 9 | * @param {Function} callback 10 | */ 11 | async function forEach (array, callback) { 12 | array = Array.isArray(array) ? array : [ array ] 13 | 14 | await Promise.all(array.map(callback)) 15 | } 16 | 17 | /** 18 | * Helper function that waits for the given number of `ms`. 19 | * 20 | * @param {Integer} ms 21 | * 22 | * @returns {Integer} 23 | */ 24 | async function wait (ms) { 25 | await new Promise(resolve => setTimeout(resolve, ms)) 26 | 27 | return ms 28 | } 29 | 30 | async function run () { 31 | const timeouts = [10, 600, 200, 775, 125, 990] 32 | 33 | console.log('Fetching data ->') 34 | await forEach(timeouts, async timeout => { 35 | console.log( 36 | `took ${await wait(timeout)} ms` 37 | ) 38 | }) 39 | } 40 | 41 | run() 42 | -------------------------------------------------------------------------------- /collections/async-array-map/async-array-map.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Axios = require('axios') 4 | 5 | async function githubDetailsFor (repo) { 6 | const response = await Axios({ 7 | method: 'GET', 8 | url: repo.url, 9 | headers: { Accept: 'application/vnd.github.v3+json' } 10 | }) 11 | 12 | return Object.assign(repo, { 13 | name: response.data.full_name, 14 | description: response.data.description 15 | }) 16 | } 17 | 18 | async function run () { 19 | const repos = [ 20 | { url: 'https://api.github.com/repos/futurestudio/futureflix-starter-kit' }, 21 | { url: 'https://api.github.com/repos/futurestudio/android-tutorials-glide' } 22 | ] 23 | 24 | const promises = repos.map(async (repo) => { 25 | return githubDetailsFor(repo) 26 | }) 27 | 28 | console.log( 29 | await Promise.all(promises) 30 | ) 31 | } 32 | 33 | run() 34 | -------------------------------------------------------------------------------- /collections/async-array-mapSeries/async-array-mapSeries.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Axios = require('axios') 4 | 5 | /** 6 | * Asynchronous version of Array#map(), running all transformations 7 | * in sequence. It runs the given `callback` on each item of 8 | * the `array` and returns an array of transformed items. 9 | * 10 | * @param {Array} array 11 | * @param {Function} callback 12 | * 13 | * @returns {Array} resulting array of transformed items 14 | */ 15 | async function mapSeries (array, callback) { 16 | const results = [] 17 | 18 | for (const index in array) { 19 | results.push( 20 | await callback(array[index], index, array) 21 | ) 22 | } 23 | 24 | return results 25 | } 26 | 27 | async function githubDetailsFor (repo) { 28 | const response = await Axios({ 29 | method: 'GET', 30 | url: repo.url, 31 | headers: { Accept: 'application/vnd.github.v3+json' } 32 | }) 33 | 34 | return Object.assign(repo, { 35 | name: response.data.full_name, 36 | description: response.data.description 37 | }) 38 | } 39 | 40 | async function run () { 41 | const repos = [ 42 | { url: 'https://api.github.com/repos/superchargejs/supercharge' }, 43 | { url: 'https://api.github.com/repos/superchargejs/collections' }, 44 | { url: 'https://api.github.com/repos/futurestudio/nodejs-tutorials' }, 45 | { url: 'https://api.github.com/repos/futurestudio/futureflix-starter-kit' } 46 | ] 47 | 48 | console.log( 49 | await mapSeries(repos, async (repo, index, array) => { 50 | return githubDetailsFor(repo) 51 | }) 52 | ) 53 | } 54 | 55 | run() 56 | -------------------------------------------------------------------------------- /collections/async-array-some/async-array-some.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Asynchronous version of `Array#some()`. The function 5 | * tests whether at least one element in the `array` 6 | * passes the check implemented by the `callback`. 7 | * 8 | * @param {Array} array 9 | * @param {Function} callback 10 | * 11 | * @returns {Boolean} 12 | */ 13 | async function some (array, callback) { 14 | array = Array.isArray(array) ? array : [ array ] 15 | 16 | const values = await Promise.all(array.map(callback)) 17 | 18 | return values.some(value => !!value) 19 | } 20 | 21 | /** 22 | * Helper function that waits for the given number of `ms`. 23 | * 24 | * @param {Integer} ms 25 | * 26 | * @returns {Integer} 27 | */ 28 | async function wait (ms) { 29 | await new Promise(resolve => setTimeout(resolve, ms)) 30 | 31 | return ms 32 | } 33 | 34 | async function run () { 35 | const timeouts = [10, 600, 200, 775, 125, 990] 36 | 37 | console.log('Are some waiting times greater than 500? ->') 38 | console.log( 39 | await some(timeouts, async timeout => { 40 | return await wait(timeout) > 500 41 | }) 42 | 43 | ) 44 | 45 | console.log('\nAre some waiting times lower then 10? ->') 46 | console.log( 47 | await some(timeouts, async timeout => { 48 | return await wait(timeout) < 10 49 | }) 50 | ) 51 | } 52 | 53 | run() 54 | -------------------------------------------------------------------------------- /data-structures/implementations/linked-list/linked-list.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Node = require('./node') 4 | 5 | class LinkedList { 6 | constructor () { 7 | this._head = null 8 | } 9 | 10 | addToHead (value) { 11 | if (this.isEmpty()) { 12 | this._head = new Node(value) 13 | return this 14 | } 15 | 16 | this._head = new Node(value, this._head) 17 | 18 | return this 19 | } 20 | 21 | addBefore (value, newValue) { 22 | if (!this.has(value)) { 23 | throw new Error(`Cannot insert ${newValue}: cannot find ${value}`) 24 | } 25 | 26 | let prevNode = null 27 | let current = this._head 28 | 29 | while (current) { 30 | if (current.getValue() === value) { 31 | if (prevNode) { 32 | prevNode.setNext(new Node(newValue, current)) 33 | } else { 34 | this.addToHead(newValue) 35 | } 36 | 37 | break 38 | } 39 | 40 | prevNode = current 41 | current = current.getNext() 42 | } 43 | 44 | return this 45 | } 46 | 47 | addAfter (value, newValue) { 48 | if (!this.has(value)) { 49 | throw new Error(`Cannot insert ${newValue}: cannot find ${value}`) 50 | } 51 | 52 | let current = this._head 53 | 54 | while (current) { 55 | if (current.getValue() === value) { 56 | current.setNext(new Node(newValue, current.getNext())) 57 | break 58 | } 59 | 60 | current = current.getNext() 61 | } 62 | 63 | return this 64 | } 65 | 66 | addToTail (value) { 67 | if (this.isEmpty()) { 68 | this._head = new Node(value) 69 | return this 70 | } 71 | 72 | let current = this._head 73 | 74 | while (current.hasNext()) { 75 | current = current.getNext() 76 | } 77 | 78 | current.setNext(new Node(value)) 79 | 80 | return this 81 | } 82 | 83 | removeHead () { 84 | if (this.isEmpty()) { 85 | return this 86 | } 87 | 88 | if (this._head.hasNext()) { 89 | this._head = this._head.getNext() 90 | 91 | return this 92 | } 93 | 94 | this._head = null 95 | 96 | return this 97 | } 98 | 99 | removeTail () { 100 | if (this.isEmpty()) { 101 | return this 102 | } 103 | 104 | let preLast = null 105 | let current = this._head 106 | 107 | while (current.hasNext()) { 108 | preLast = current 109 | current = current.getNext() 110 | } 111 | 112 | if (preLast) { 113 | preLast.setNext(null) 114 | return this 115 | } 116 | 117 | this.removeHead() 118 | 119 | return this 120 | } 121 | 122 | remove (value) { 123 | if (this.isEmpty()) { 124 | return this 125 | } 126 | 127 | if (!this.has(value)) { 128 | throw new Error(`Cannot find ${value} and therefore not remove it`) 129 | } 130 | 131 | let prevNode = null 132 | let current = this._head 133 | 134 | while (current) { 135 | if (current.getValue() === value) { 136 | break 137 | } 138 | 139 | prevNode = current 140 | current = current.getNext() 141 | } 142 | 143 | if (prevNode) { 144 | prevNode.setNext(current.getNext()) 145 | return this 146 | } 147 | 148 | this.removeHead() 149 | 150 | return this 151 | } 152 | 153 | has (value) { 154 | let current = this._head 155 | 156 | while (current) { 157 | if (current.getValue() === value) { 158 | return true 159 | } 160 | 161 | current = current.getNext() 162 | } 163 | 164 | return false 165 | } 166 | 167 | traverse (callback) { 168 | let current = this._head 169 | 170 | while (current) { 171 | callback(current.getValue()) 172 | current = current.getNext() 173 | } 174 | } 175 | 176 | toArray () { 177 | let result = [] 178 | this.traverse(value => result.push(value)) 179 | 180 | return result 181 | } 182 | 183 | size () { 184 | let size = 0 185 | 186 | this.traverse(() => { 187 | size += 1 188 | }) 189 | 190 | return size 191 | } 192 | 193 | isEmpty () { 194 | return this.size() === 0 195 | } 196 | 197 | clear () { 198 | this._head = null 199 | } 200 | } 201 | 202 | module.exports = LinkedList 203 | -------------------------------------------------------------------------------- /data-structures/implementations/linked-list/node.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Node { 4 | constructor (value, next) { 5 | this.value = value 6 | this.next = next 7 | } 8 | 9 | getValue () { 10 | return this.value 11 | } 12 | 13 | getNext () { 14 | return this.next 15 | } 16 | 17 | setNext (next) { 18 | this.next = next 19 | } 20 | 21 | hasNext () { 22 | return !!this.next 23 | } 24 | } 25 | 26 | module.exports = Node 27 | -------------------------------------------------------------------------------- /data-structures/implementations/priority-queue/priority-queue.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class PriorityQueue { 4 | /** 5 | * Creates a new PriorityQueue instance. 6 | * 7 | * @param {Mixed} items 8 | */ 9 | constructor () { 10 | this._queue = [] 11 | } 12 | 13 | /** 14 | * Pushes a new `item` with the related 15 | * `priority` into the queue. 16 | * 17 | * @param {Mixed} items 18 | */ 19 | enqueue (item, priority) { 20 | if (Number.isNaN(+priority) || priority < 1) { 21 | throw new Error('Failed to enqueue item: the priority must be a positive number') 22 | } 23 | 24 | this._queue.push({ priority, item }) 25 | 26 | this._queue.sort((a, b) => { 27 | return a.priority - b.priority 28 | }) 29 | 30 | return this 31 | } 32 | 33 | /** 34 | * Removes and returns the item which is up 35 | * for processing. Returns `undefined` 36 | * if the queue is empty. 37 | * 38 | * @returns {Mixed} 39 | */ 40 | dequeue () { 41 | if (this.isEmpty()) { 42 | return 43 | } 44 | 45 | const { item } = this._queue.shift() 46 | 47 | return item 48 | } 49 | 50 | /** 51 | * Returns the front item without removing it 52 | * from the queue. Returns `undefined` if 53 | * the queue is empty. 54 | * 55 | * @returns {Mixed} 56 | */ 57 | peek () { 58 | return this._queue[0] && this._queue[0].item 59 | } 60 | 61 | /** 62 | * Returns the number of items in the queue. 63 | * 64 | * @returns {Integer} 65 | */ 66 | size () { 67 | return this._queue.length 68 | } 69 | 70 | /** 71 | * Returns `true` if there are no items in 72 | * the queue, `false` otherwise. 73 | * 74 | * @returns {Boolean} 75 | */ 76 | isEmpty () { 77 | return this.size() === 0 78 | } 79 | 80 | /** 81 | * Removes all items from the queue. 82 | */ 83 | clear () { 84 | this._queue = [] 85 | } 86 | } 87 | 88 | module.exports = PriorityQueue 89 | -------------------------------------------------------------------------------- /data-structures/implementations/queue/queue.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Queue { 4 | /** 5 | * Creates a new Queue instance and 6 | * enqueues the given `items`. 7 | * 8 | * @param {Mixed} items 9 | */ 10 | constructor (...items) { 11 | this._queue = [...items] 12 | } 13 | 14 | /** 15 | * Pushes new `items` into the queue. 16 | * 17 | * @param {Mixed} items 18 | */ 19 | enqueue (...items) { 20 | this._queue.push(...items) 21 | } 22 | 23 | /** 24 | * Removes and returns the item which is up 25 | * for processing. Returns `undefined` 26 | * if the queue is empty. 27 | * 28 | * @returns {Mixed} 29 | */ 30 | dequeue () { 31 | return this._queue.shift() 32 | } 33 | 34 | /** 35 | * Returns the front item without removing it 36 | * from the queue. Returns `undefined` if 37 | * the queue is empty. 38 | * 39 | * @returns {Mixed} 40 | */ 41 | peek () { 42 | return this._queue[0] 43 | } 44 | 45 | /** 46 | * Returns the number of items in the queue. 47 | * 48 | * @returns {Integer} 49 | */ 50 | size () { 51 | return this._queue.length 52 | } 53 | 54 | /** 55 | * Returns `true` if there are no items in 56 | * the queue, `false` otherwise. 57 | * 58 | * @returns {Boolean} 59 | */ 60 | isEmpty () { 61 | return this.size() === 0 62 | } 63 | 64 | /** 65 | * Removes all items from the queue. 66 | */ 67 | clear () { 68 | this._queue = [] 69 | } 70 | } 71 | 72 | module.exports = Queue 73 | -------------------------------------------------------------------------------- /data-structures/implementations/set/set.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Set { 4 | /** 5 | * Creates a new Set instance and 6 | * adds the given `items`. 7 | * 8 | * @param {Mixed} items 9 | */ 10 | 11 | constructor (...items) { 12 | this._set = {} 13 | this._size = 0 14 | 15 | this.add(...items) 16 | } 17 | 18 | /** 19 | * Adds new `items` into the set. 20 | * 21 | * @param {Mixed} items 22 | */ 23 | add (...items) { 24 | items.forEach(item => { 25 | if (!this.has(item)) { 26 | this._set[item] = item 27 | this._size += 1 28 | } 29 | }) 30 | } 31 | 32 | /** 33 | * Removes the given `item` from the set. 34 | */ 35 | remove (item) { 36 | if (this.has(item)) { 37 | this._set[item] = false 38 | this._size -= 1 39 | } 40 | } 41 | 42 | /** 43 | * Returns `true` if the given `item` is 44 | * contained in the set, `false` 45 | * otherwise. 46 | * 47 | * @param {Mixed} item 48 | * 49 | * @returns {Boolean} 50 | */ 51 | has (item) { 52 | return !!this._set[item] 53 | } 54 | 55 | /** 56 | * Returns an array containing all the values 57 | * contained in the set. 58 | * 59 | * @returns {Array} 60 | */ 61 | toArray () { 62 | return Object.values(this._set).map((el) => { 63 | return Number.isNaN(+el) ? el : +el 64 | }) 65 | } 66 | 67 | /** 68 | * Returns the number of items in the set. 69 | * 70 | * @returns {Integer} 71 | */ 72 | size () { 73 | return this._size 74 | } 75 | 76 | /** 77 | * Returns `true` if there are no items in 78 | * the set, `false` otherwise. 79 | * 80 | * @returns {Boolean} 81 | */ 82 | isEmpty () { 83 | return this.size() === 0 84 | } 85 | 86 | /** 87 | * Removes all items from the set. 88 | */ 89 | clear () { 90 | this._set = {} 91 | this._size = 0 92 | } 93 | } 94 | 95 | module.exports = Set 96 | -------------------------------------------------------------------------------- /data-structures/implementations/stack/stack.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Stack { 4 | /** 5 | * Creates a new Stack instance and 6 | * pushes the given `items` onto 7 | * the stack. 8 | * 9 | * @param {Mixed} items 10 | */ 11 | constructor (...items) { 12 | this._stack = [...items.reverse()] 13 | } 14 | 15 | /** 16 | * Push new `items` onto the stack. 17 | * 18 | * @param {Mixed} items 19 | */ 20 | push (...items) { 21 | this._stack.unshift(...items.reverse()) 22 | } 23 | 24 | /** 25 | * Removes and returns the most-recently added 26 | * item from the stack. Returns `undefined` 27 | * if the stack is empty. 28 | * 29 | * @returns {Mixed} 30 | */ 31 | pop () { 32 | return this._stack.shift() 33 | } 34 | 35 | /** 36 | * Returns the most-recently added item from 37 | * the stack. This method won’t remove the 38 | * returned item. Returns `undefined` 39 | * if the stack is empty. 40 | * 41 | * @returns {Mixed} 42 | */ 43 | peek () { 44 | return this._stack[0] 45 | } 46 | 47 | /** 48 | * Returns the number of items on the stack. 49 | * 50 | * @returns {Integer} 51 | */ 52 | size () { 53 | return this._stack.length 54 | } 55 | 56 | /** 57 | * Returns `true` if there are no items on 58 | * the stack, `false` otherwise. 59 | * 60 | * @returns {Boolean} 61 | */ 62 | isEmpty () { 63 | return this.size() === 0 64 | } 65 | 66 | /** 67 | * Removes all items from the stack. 68 | */ 69 | clear () { 70 | this._stack = [] 71 | } 72 | } 73 | 74 | module.exports = Stack 75 | -------------------------------------------------------------------------------- /data-structures/starter-files/linked-list.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class LinkedList { 4 | constructor () { 5 | this._head = null 6 | } 7 | 8 | addToHead (value) {} 9 | addBefore (value, newValue) {} 10 | addAfter (value, newValue) {} 11 | addToTail (value) {} 12 | removeHead () {} 13 | removeTail () {} 14 | remove (value) {} 15 | has (value) {} 16 | toArray () {} 17 | size () {} 18 | isEmpty () {} 19 | clear () {} 20 | } 21 | 22 | module.exports = LinkedList 23 | -------------------------------------------------------------------------------- /data-structures/starter-files/priority-queue.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class PriorityQueue { 4 | constructor () { 5 | this._queue = [] 6 | } 7 | 8 | enqueue (item, priority) {} 9 | dequeue () {} 10 | peek () {} 11 | size () {} 12 | isEmpty () {} 13 | clear () {} 14 | } 15 | 16 | module.exports = PriorityQueue 17 | -------------------------------------------------------------------------------- /data-structures/starter-files/queue.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Queue { 4 | constructor (...items) { 5 | this._queue = [] 6 | } 7 | 8 | enqueue (...items) {} 9 | dequeue () {} 10 | peek () {} 11 | size () {} 12 | isEmpty () {} 13 | clear () {} 14 | } 15 | 16 | module.exports = Queue 17 | -------------------------------------------------------------------------------- /data-structures/starter-files/set.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Set { 4 | constructor (...items) { 5 | this._set = {} 6 | } 7 | 8 | add (...items) {} 9 | remove (item) {} 10 | has (item) {} 11 | toArray () {} 12 | size () {} 13 | isEmpty () {} 14 | clear () {} 15 | } 16 | 17 | module.exports = Set 18 | -------------------------------------------------------------------------------- /data-structures/starter-files/stack.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Stack { 4 | constructor (...items) { 5 | this._stack = [] 6 | } 7 | 8 | push (...items) {} 9 | pop () {} 10 | peek () {} 11 | size () {} 12 | isEmpty () {} 13 | clear () {} 14 | } 15 | 16 | module.exports = Stack 17 | -------------------------------------------------------------------------------- /date/increase-date-by-one-week.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const today = new Date() 4 | const nextWeek = new Date() 5 | 6 | // add 7 days to the current date 7 | nextWeek.setDate(today.getDate() + 7) 8 | 9 | console.log(nextWeek) 10 | -------------------------------------------------------------------------------- /date/tomorrows-date.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const today = new Date() 4 | const tomorrow = new Date() 5 | 6 | // add 1 day to today 7 | tomorrow.setDate(today.getDate() + 1) 8 | 9 | console.log(tomorrow) 10 | -------------------------------------------------------------------------------- /error-handling/unhandled-rejection.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Youch = require('youch') 4 | const forTerminal = require('youch-terminal') 5 | 6 | process.on('unhandledRejection', async (error) => { 7 | const output = await new Youch(error).toJSON() 8 | console.log(forTerminal(output)) 9 | process.exit(1) 10 | }) 11 | 12 | async function start () { 13 | return Promise.reject( 14 | new Error('Missing coffee exception') 15 | ) 16 | } 17 | 18 | start() 19 | -------------------------------------------------------------------------------- /events/async-event-listener/event.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const EventEmitter = require('events') 4 | 5 | class Clock extends EventEmitter { 6 | constructor () { 7 | super() 8 | 9 | this.timer = null 10 | this.ticks = null 11 | } 12 | 13 | start () { 14 | if (!this.timer) { 15 | this.timer = setInterval(() => this.tick(), 1000) 16 | } 17 | 18 | this.emit('start', { startsAt: new Date() }) 19 | } 20 | 21 | stop () { 22 | clearInterval(this.timer) 23 | this.timer = null 24 | this.emit('stop', { endsAt: new Date() }) 25 | } 26 | 27 | tick () { 28 | this.ticks++ 29 | this.emit('tick', { now: new Date(), tick: this.ticks }) 30 | 31 | if (this.ticks > 9) { 32 | this.stop() 33 | } 34 | } 35 | } 36 | 37 | module.exports = Clock 38 | -------------------------------------------------------------------------------- /events/async-event-listener/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Event = require('./event') 4 | const Listener = require('./listener') 5 | 6 | const clock = new Event() 7 | const handler = new Listener() 8 | 9 | clock.on('start', async (...args) => { 10 | await handler.onStart(...args) 11 | }) 12 | 13 | clock.on('stop', async (...args) => { 14 | await handler.onStop(...args) 15 | }) 16 | 17 | clock.on('tick', async (...args) => { 18 | await handler.onTick(...args) 19 | }) 20 | 21 | clock.start() 22 | -------------------------------------------------------------------------------- /events/async-event-listener/listener.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Listener { 4 | onStart () { 5 | console.log(`Opening DevDays Pizza House\n`) 6 | } 7 | 8 | onStop () { 9 | console.log(`\nDevDays Pizza is going home\n`) 10 | } 11 | 12 | async onTick ({ tick }) { 13 | const bakingTime = this._randomTimeout() 14 | console.log(`📥 #${tick} received -> delivery takes ${bakingTime} s`) 15 | 16 | await this._workHardBakeHard(bakingTime) 17 | 18 | console.log(`🍕 ${tick} delivered -> took just: ${bakingTime} s`) 19 | } 20 | 21 | async _workHardBakeHard (seconds) { 22 | await new Promise(resolve => { 23 | setTimeout(resolve, seconds * 1000) 24 | }) 25 | } 26 | 27 | _randomTimeout () { 28 | return Math.floor(Math.random() * 20) 29 | } 30 | } 31 | 32 | module.exports = Listener 33 | -------------------------------------------------------------------------------- /filesystem/create-an-empty-file/.gitignore: -------------------------------------------------------------------------------- 1 | touched.sync 2 | async.touch 3 | -------------------------------------------------------------------------------- /filesystem/create-an-empty-file/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Fs = require('fs-extra') 4 | 5 | run() 6 | 7 | async function run () { 8 | touchSync('./touched.sync') 9 | console.log('created "touched.sync"') 10 | 11 | await touch('./async.touch') 12 | console.log('created "async.touch"') 13 | } 14 | 15 | /** 16 | * Creates an empty file at `path`. This method does not ensure to create 17 | * missing directories along the path. It won’t override an existing 18 | * file or modify the content. Use the "w" flag to override an 19 | * existing file and its content. 20 | * 21 | * @param {String} path 22 | * 23 | */ 24 | function touchSync (path) { 25 | Fs.closeSync(Fs.openSync(path, 'a')) 26 | } 27 | 28 | /** 29 | * Ensures that the file at `path` exists. It will also create 30 | * missing directories along the path. If a file at `path` 31 | * already exists, it won’t modify the content. 32 | * 33 | * @param {String} path 34 | */ 35 | async function touch (path) { 36 | await Fs.ensureFile(path) 37 | } 38 | -------------------------------------------------------------------------------- /filesystem/file-created-date/content.txt: -------------------------------------------------------------------------------- 1 | example file for "file-created-date" 2 | -------------------------------------------------------------------------------- /filesystem/file-created-date/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Fs = require('fs') 4 | const Path = require('path') 5 | 6 | function createdDate (file) { 7 | const { birthtime } = Fs.statSync(file) 8 | 9 | return birthtime 10 | } 11 | 12 | const file = Path.resolve(__dirname, 'content.txt') 13 | 14 | console.log( 15 | createdDate(file) 16 | ) 17 | -------------------------------------------------------------------------------- /filesystem/file-last-updated-date/content.txt: -------------------------------------------------------------------------------- 1 | example file for "file-last-updated-date" 2 | -------------------------------------------------------------------------------- /filesystem/file-last-updated-date/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Fs = require('fs') 4 | const Path = require('path') 5 | 6 | function lastUpdatedDate (file) { 7 | const { ctime, mtime } = Fs.statSync(file) 8 | console.log(`File data last modified: ${mtime}`) 9 | console.log(`File status last modified: ${ctime}`) 10 | 11 | return mtime 12 | } 13 | 14 | const file = Path.resolve(__dirname, 'content.txt') 15 | 16 | lastUpdatedDate(file) 17 | -------------------------------------------------------------------------------- /filesystem/write-json-object-to-file/content.txt: -------------------------------------------------------------------------------- 1 | { 2 | "name": "promisified Marcus" 3 | } -------------------------------------------------------------------------------- /filesystem/write-json-object-to-file/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Fs = require('fs') 4 | const Path = require('path') 5 | 6 | const filePath = Path.resolve(__dirname, 'content.txt') 7 | 8 | function writeToFile (path, data) { 9 | const json = JSON.stringify(data, null, 2) 10 | 11 | Fs.writeFile(path, json, (err) => { 12 | if (err) { 13 | console.error(err) 14 | throw err 15 | } 16 | 17 | console.log('Saved data to file.') 18 | }) 19 | } 20 | 21 | function readFromFile (path) { 22 | Fs.readFile(path, 'utf8', (err, json) => { 23 | if (err) { 24 | console.error(err) 25 | throw err 26 | } 27 | 28 | const data = JSON.parse(json) 29 | console.log(data) 30 | }) 31 | } 32 | 33 | writeToFile(filePath, { name: 'Marcus' }) 34 | readFromFile(filePath) 35 | -------------------------------------------------------------------------------- /filesystem/write-json-object-to-file/promisified.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Path = require('path') 4 | const Fs = require('fs-extra') 5 | 6 | const filePath = Path.resolve(__dirname, 'content.txt') 7 | 8 | async function writeToFile (path, data) { 9 | const json = JSON.stringify(data, null, 2) 10 | 11 | try { 12 | await Fs.writeFile(path, json) 13 | console.log('Saved data to file.') 14 | } catch (error) { 15 | console.error(error) 16 | } 17 | } 18 | 19 | async function dataFromFile (path) { 20 | try { 21 | const json = await Fs.readFile(path, 'utf8') 22 | const content = JSON.parse(json) 23 | console.log(content) 24 | } catch (error) { 25 | console.log(error) 26 | } 27 | } 28 | 29 | writeToFile(filePath, { name: 'promisified Marcus' }) 30 | dataFromFile(filePath) 31 | -------------------------------------------------------------------------------- /flow-control/async-constructors/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SampleDriver = require('./sample-cache-driver') 4 | 5 | class Cache { 6 | constructor (options) { 7 | this.options = options 8 | this.driver = new SampleDriver() 9 | 10 | /** 11 | * Node.js doesn’t support async constructors. 12 | * You can’t await driver.start() here. 13 | * Instead, use an async method to run the initialization. 14 | */ 15 | } 16 | 17 | async init () { 18 | await this.driver.start() 19 | } 20 | 21 | static async init (options) { 22 | return new this(options).init() 23 | 24 | // the caller can do something like this: 25 | // const cache = await Cache.init({ options }) 26 | // or 27 | // const cache = await new Cache({options}).init() 28 | } 29 | } 30 | 31 | module.exports = Cache 32 | -------------------------------------------------------------------------------- /flow-control/async-constructors/sample-cache-driver.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class CacheDriver { 4 | constructor (options) { 5 | this.options = options 6 | } 7 | 8 | async start () {} 9 | async stop () {} 10 | 11 | async get () {} 12 | async forget () {} 13 | async remember () {} 14 | } 15 | 16 | module.exports = CacheDriver 17 | -------------------------------------------------------------------------------- /flow-control/promise-and-callback/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const packageInfo = require('./promise-and-callback.js').packageInfo 4 | const packageInfoPromisified = require('./promise-and-callback.js').packageInfoPromisified 5 | 6 | packageInfo() 7 | .then(data => { 8 | console.log('promise finished') 9 | console.log(data.toString()) 10 | }) 11 | .catch(err => { 12 | console.log(err.message) 13 | }) 14 | 15 | packageInfo((err, data) => { 16 | console.log('callback available') 17 | if (err) { 18 | throw err 19 | } 20 | 21 | if (data) console.log(data.toString()) 22 | }) 23 | 24 | packageInfoPromisified() 25 | .then(data => { 26 | console.log('promisified version of package info read finished') 27 | console.log(data.toString()) 28 | }) 29 | .catch(err => { 30 | console.log(err.message) 31 | }) 32 | -------------------------------------------------------------------------------- /flow-control/promise-and-callback/promise-and-callback.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Fs = require('fs') 4 | const Path = require('path') 5 | const Util = require('util') 6 | const packagePath = Path.resolve(__dirname, '..', '..', 'package.json') 7 | 8 | exports.packageInfo = function (callback) { 9 | return new Promise((resolve, reject) => { 10 | Fs.readFile(packagePath, (err, data) => { 11 | if (err) { 12 | // if no callback available, reject the promise 13 | // else, return callback using "error-first-pattern" 14 | return callback ? callback(err) : reject(err) 15 | } 16 | 17 | return callback ? callback(null, data) : resolve(data) 18 | }) 19 | }) 20 | } 21 | 22 | exports.packageInfoPromisified = function () { 23 | const readFilePromise = Util.promisify(Fs.readFile) 24 | return readFilePromise(packagePath) 25 | } 26 | -------------------------------------------------------------------------------- /flow-control/promises-in-parallel/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Hoek = require('@hapi/hoek') 4 | 5 | run() 6 | 7 | async function run () { 8 | const timeouts = [10, 600, 200, 775, 125, 990] 9 | 10 | const forResult = await forLoopInParallel(timeouts) 11 | console.log() 12 | 13 | const allResult = await awaitAll(timeouts) 14 | console.log() 15 | 16 | console.log('YES! All done. Here are the results') 17 | console.log(forResult) 18 | console.log(allResult) 19 | } 20 | 21 | async function forLoopInParallel (timeouts) { 22 | const promises = timeouts.map(timeout => asyncProcessing(timeout)) 23 | const result = [] 24 | 25 | for (const timeoutPromise of promises) { 26 | result.push(await timeoutPromise) 27 | } 28 | 29 | return result 30 | } 31 | 32 | async function awaitAll (timeouts) { 33 | const promises = timeouts.map(timeout => asyncProcessing(timeout)) 34 | const result = await Promise.all(promises) 35 | 36 | return result 37 | } 38 | 39 | async function asyncProcessing (ms) { 40 | await Hoek.wait(ms) 41 | console.log(`waited: ${ms}ms`) 42 | 43 | return ms 44 | } 45 | -------------------------------------------------------------------------------- /flow-control/promises-in-parallel/with-errors.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Hoek = require('@hapi/hoek') 4 | 5 | run() 6 | 7 | async function run () { 8 | const timeouts = [10, 600, 200, 775, 125, 990] 9 | const promises = timeouts.map(timeout => asyncProcessing(timeout)) 10 | 11 | try { 12 | const result = await Promise.all( 13 | promises.concat([ throwUp() ]) 14 | ) 15 | 16 | /** 17 | * The control flow is not what you expect if one of the promises 18 | * within Promise.all rejects. If at least a single promise 19 | * rejects, you won't see the result logged to the console. 20 | */ 21 | console.log('Here is the result:') 22 | console.log(result) 23 | } catch (error) { 24 | console.log('WAT WAT WAT ?! Here’s an error. Holy moly!!') 25 | 26 | console.log(error) 27 | } 28 | 29 | console.log() 30 | console.log('YES! All done.') 31 | } 32 | 33 | async function throwUp () { 34 | throw new Error('throwing up') 35 | } 36 | 37 | async function asyncProcessing (timeout) { 38 | await Hoek.wait(timeout) 39 | console.log(`waited: ${timeout}ms`) 40 | } 41 | -------------------------------------------------------------------------------- /flow-control/promises-in-sequence/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Hoek = require('@hapi/hoek') 4 | 5 | run() 6 | 7 | async function run () { 8 | const timeouts = [10, 600, 200, 775, 125, 990] 9 | 10 | const forResult = await forLoopInSequence(timeouts) 11 | console.log() 12 | 13 | const reduceResult = await reduceInSequence(timeouts) 14 | console.log() 15 | 16 | console.log('YES! All done. Here are the results:') 17 | console.log(forResult) 18 | console.log(reduceResult) 19 | } 20 | 21 | async function forLoopInSequence (timeouts) { 22 | let result = [] 23 | 24 | for (const timeout of timeouts) { 25 | result.push(await asyncProcessing(timeout)) 26 | } 27 | 28 | return result 29 | } 30 | 31 | async function reduceInSequence (timeouts) { 32 | return timeouts.reduce(async (carry, timeout) => { 33 | return [] 34 | .concat(await carry) 35 | .concat(await asyncProcessing(timeout)) 36 | }, []) 37 | } 38 | 39 | async function asyncProcessing (ms) { 40 | await Hoek.wait(ms) 41 | console.log(`waited: ${ms}`) 42 | 43 | return ms 44 | } 45 | -------------------------------------------------------------------------------- /flow-control/promises-in-sequence/with-errors.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Hoek = require('@hapi/hoek') 4 | 5 | run() 6 | 7 | async function run () { 8 | const timeouts = [10, 600, 200, 775, throwUp, 125, 990] 9 | const result = await reduceInSequence(timeouts) 10 | 11 | console.log() 12 | console.log('YES! All done. Here is the result:') 13 | console.log(result) 14 | } 15 | 16 | async function throwUp () { 17 | throw new Error('throwing up') 18 | } 19 | 20 | async function reduceInSequence (timeouts) { 21 | return timeouts.reduce(async (carry, timeout) => { 22 | return [ 23 | ...(await carry), 24 | await asyncProcessing(timeout) 25 | ] 26 | }, Promise.resolve([])) 27 | } 28 | 29 | async function asyncProcessing (ms) { 30 | if (typeof ms === 'function') { 31 | return ms() 32 | } 33 | 34 | await Hoek.wait(ms) 35 | console.log(`finished: ${ms}ms`) 36 | } 37 | -------------------------------------------------------------------------------- /flow-control/promises-waterfall/.gitignore: -------------------------------------------------------------------------------- 1 | data.json 2 | -------------------------------------------------------------------------------- /flow-control/promises-waterfall/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Path = require('path') 4 | const Fs = require('fs-extra') 5 | 6 | saveIt() 7 | 8 | async function saveIt () { 9 | const content = await downloadContent() 10 | const file = await saveToFile(content) 11 | const { size } = await Fs.stat(file) 12 | 13 | console.log(`File content is HUGE: ${(size / 1000).toFixed(2)} KB`) 14 | } 15 | async function downloadContent () { 16 | return JSON.stringify([ 17 | { id: 1, description: 'huge JSON content' }, 18 | { id: 2, description: 'more more more!' }, 19 | { id: 3, description: 'you rock buddy!' } 20 | ]) 21 | } 22 | 23 | async function saveToFile (content) { 24 | const file = Path.resolve(__dirname, 'data.json') 25 | await Fs.writeFile(file, content) 26 | 27 | return file 28 | } 29 | -------------------------------------------------------------------------------- /loops/for-of-vs-for-in/for-of-vs-for-in.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const types = [ 'object', 'array', 'string', 'integer', 'float', 'boolean' ] 4 | 5 | /** 6 | * Use `for … of` to iterate through the values of an iterable. 7 | * Use for-of on arrays to access the values. Notice that an 8 | * object is not an iterable, but a Map is. 9 | */ 10 | for (const type of types) { 11 | console.log(`A JavaScript type is: ${type}`) 12 | } 13 | 14 | const cache = new Map() 15 | cache.set('posts:1', { id: 1, title: 'Post 1' }) 16 | cache.set('posts:2', { id: 2, title: 'Post 2' }) 17 | 18 | for (const item of cache.values()) { 19 | console.log(`Cache item: ${JSON.stringify(item)}`) 20 | } 21 | 22 | /** 23 | * When iterating through a Map, you’ll receive each item as an array 24 | * with two entries. The first entry in the array is the key and 25 | * the second entry is the related value. The following example 26 | * uses destructuring to assign the properties. 27 | */ 28 | for (const [ key, value ] of cache) { 29 | console.log(`Cache item: "${key}" with values ${JSON.stringify(value)}`) 30 | } 31 | 32 | /** 33 | * Use `for … in` to iterate through the keys of an iterable. 34 | * Using for-in on an object will return the properties. 35 | * On arrays, for-in returns the item’s index. 36 | */ 37 | const user = { name: 'Marcus', likes: 'Node.js' } 38 | 39 | for (const key in user) { 40 | console.log(`${key}: ${user[key]}`) 41 | } 42 | 43 | const names = [ 'Marcus', 'Norman', 'Christian' ] 44 | 45 | for (const index in names) { 46 | console.log(`${names[index]} is at position ${index}`) 47 | } 48 | -------------------------------------------------------------------------------- /misc/custom-error/error.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class NotEnoughCoffee extends Error { 4 | constructor (message) { 5 | super(message) 6 | 7 | this.name = this.constructor.name 8 | } 9 | } 10 | 11 | module.exports = NotEnoughCoffee 12 | -------------------------------------------------------------------------------- /misc/custom-error/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const NotEnoughCoffee = require('./error') 4 | 5 | throw new NotEnoughCoffee('Well, you may need another coffee :)') 6 | -------------------------------------------------------------------------------- /misc/increased-memory-limit/show-memory-limit.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const v8 = require('v8') 4 | 5 | // console.log(v8.getHeapStatistics()) 6 | 7 | const totalHeapSize = v8.getHeapStatistics().total_available_size 8 | let totalHeapSizeInGB = (totalHeapSize / 1024 / 1024 / 1024).toFixed(2) 9 | 10 | console.log(`Total heap size (bytes) ${totalHeapSize}, (GB ~${totalHeapSizeInGB})`) 11 | -------------------------------------------------------------------------------- /objects/deep-merge.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const merge = require('deepmerge') 4 | 5 | const first = { 6 | name: 'Marcus', 7 | sub: { 8 | eyes: 'blue' 9 | } 10 | } 11 | 12 | const second = { 13 | name: 'Node.js', 14 | sub: { 15 | hair: 'brown' 16 | } 17 | } 18 | 19 | console.log( 20 | merge(first, second) 21 | ) 22 | -------------------------------------------------------------------------------- /objects/merge-objects.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const first = { 4 | name: 'Marcus', 5 | lovesNode: true, 6 | sub: { 7 | eyes: 'blue' 8 | } 9 | } 10 | 11 | const second = { 12 | name: 'Node.js', 13 | isDeveloper: false, 14 | sub: { 15 | hair: 'brown' 16 | } 17 | } 18 | 19 | /** 20 | * Object.assign merges all arguments into the first one. 21 | * It overrides the properties in the first arguments 22 | */ 23 | console.log( 24 | Object.assign({}, first, second) 25 | ) 26 | 27 | console.log( 28 | { ...first, ...second } 29 | ) 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-tutorials", 3 | "description": "Future Studio tutorials focussed on Node.js", 4 | "version": "1.0.0", 5 | "author": "Future Studio ", 6 | "bugs": { 7 | "url": "https://github.com/futurestudio/nodejs-tutorials/issues" 8 | }, 9 | "contributors": [ 10 | { 11 | "name": "Marcus Poehls", 12 | "email": "marcus@futurestud.io" 13 | } 14 | ], 15 | "engines": { 16 | "node": ">=8.0.0" 17 | }, 18 | "dependencies": { 19 | "@hapi/hoek": "~7.1.0", 20 | "axios": "~0.19.0", 21 | "deepmerge": "~4.2.2", 22 | "fs-extra": "~8.0.1", 23 | "listr": "~0.14.3", 24 | "lodash": "~4.17.11", 25 | "p-iteration": "~1.1.8", 26 | "progress": "~2.0.3", 27 | "youch": "~2.0.10", 28 | "youch-terminal": "~1.0.0" 29 | }, 30 | "devDependencies": { 31 | "@hapi/code": "~5.3.1", 32 | "@hapi/lab": "~19.0.1", 33 | "eslint": "~5.16.0", 34 | "eslint-config-standard": "~12.0.0", 35 | "eslint-plugin-import": "~2.17.3", 36 | "eslint-plugin-node": "~9.1.0", 37 | "eslint-plugin-promise": "~4.1.1", 38 | "eslint-plugin-standard": "~4.0.0" 39 | }, 40 | "homepage": "https://github.com/futurestudio/nodejs-tutorials#readme", 41 | "license": "MIT", 42 | "main": "index.js", 43 | "repository": { 44 | "type": "git", 45 | "url": "git+https://github.com/futurestudio/nodejs-tutorials.git" 46 | }, 47 | "scripts": { 48 | "lint": "eslint --fix", 49 | "test": "lab --assert @hapi/code --leaks --coverage --lint --reporter console --output stdout --reporter html --output ./coverage/coverage.html" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /streaming/async-readline/async-stringify-transform.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Hoek = require('@hapi/hoek') 4 | const { Transform } = require('stream') 5 | 6 | class Stringifier extends Transform { 7 | async _transform (chunk, _, next) { 8 | const transformed = await this._process(chunk.toString().trim()) 9 | 10 | next(null, transformed) 11 | } 12 | 13 | async _process (message) { 14 | console.log(`\nStart processing #${message}`) 15 | await Hoek.wait(2000) 16 | 17 | return JSON.stringify({ message }).concat('\n') 18 | } 19 | } 20 | 21 | module.exports = Stringifier 22 | -------------------------------------------------------------------------------- /streaming/async-readline/input.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Stringifier = require('./async-stringify-transform') 4 | 5 | console.log('Just type what you want!') 6 | 7 | process.stdin 8 | .pipe(new Stringifier()) 9 | .pipe(process.stdout) 10 | -------------------------------------------------------------------------------- /strings/json-stringify-with-spaces-and-line-breaks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const data = { 4 | name: 'Marcus', 5 | passion: 'Future Studio', 6 | likes: [ 7 | { 8 | tag: 'Node.js', 9 | level: 10 10 | }, { 11 | tag: 'hapi', 12 | level: 10 13 | } 14 | ] 15 | } 16 | 17 | console.log( 18 | JSON.stringify(data, null, 2) 19 | ) 20 | -------------------------------------------------------------------------------- /strings/string-replace-all-appearances/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const string = 'e851e2fa-4f00-4609-9dd2-9b3794c59619' 4 | 5 | console.log(string.replace('-', '')) 6 | // -> e851e2fa4f00-4609-9dd2-9b3794c59619 7 | 8 | console.log(string.replace(/-/g, '')) 9 | // -> e851e2fa4f0046099dd29b3794c59619 10 | -------------------------------------------------------------------------------- /test/data-structures/linked-list.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Lab = require('@hapi/lab') 4 | const { expect } = require('@hapi/code') 5 | const LinkedList = require('../../data-structures/implementations/linked-list/linked-list') 6 | 7 | const { describe, it } = (exports.lab = Lab.script()) 8 | 9 | describe('LinkedList', () => { 10 | it('.addToHead()', async () => { 11 | const list = new LinkedList() 12 | list.addToHead(1).addToHead(2) 13 | expect(list.size()).to.equal(2) 14 | expect(list.toArray()).to.equal([ 2, 1 ]) 15 | }) 16 | 17 | it('.addBefore()', async () => { 18 | const list = new LinkedList() 19 | list.addToHead(2).addToTail(4).addBefore(2, 1).addBefore(4, 3) 20 | expect(list.size()).to.equal(4) 21 | expect(list.toArray()).to.equal([ 1, 2, 3, 4 ]) 22 | 23 | try { 24 | list.addBefore(10, 2) 25 | expect(true).to.be.false() // this should not be reached 26 | } catch (error) { 27 | expect(error).to.exist() 28 | } 29 | }) 30 | 31 | it('.addAfter()', async () => { 32 | const list = new LinkedList() 33 | list.addToHead(1).addToTail(2).addToTail(4) 34 | list.addAfter(2, 3) 35 | expect(list.size()).to.equal(4) 36 | expect(list.toArray()).to.equal([ 1, 2, 3, 4 ]) 37 | 38 | try { 39 | list.addAfter(10, 2) 40 | expect(true).to.be.false() // this should not be reached 41 | } catch (error) { 42 | expect(error).to.exist() 43 | } 44 | 45 | const oneTwo = new LinkedList() 46 | oneTwo.addToHead(1).addAfter(1, 2) 47 | 48 | expect(oneTwo.size()).to.equal(2) 49 | expect(oneTwo.toArray()).to.equal([ 1, 2 ]) 50 | }) 51 | 52 | it('.addToTail()', async () => { 53 | const list = new LinkedList() 54 | list.addToTail(1).addToTail(2) 55 | expect(list.size()).to.equal(2) 56 | expect(list.toArray()).to.equal([ 1, 2 ]) 57 | }) 58 | 59 | it('.removeHead()', async () => { 60 | const list = new LinkedList() 61 | list.removeHead().addToHead(1).addToTail(2).addToTail(3) 62 | list.removeHead() 63 | expect(list.size()).to.equal(2) 64 | expect(list.has(1)).to.be.false() 65 | expect(list.has(2)).to.be.true() 66 | expect(list.has(3)).to.be.true() 67 | }) 68 | 69 | it('.removeTail()', async () => { 70 | const list = new LinkedList() 71 | list.removeTail().addToHead(1).addToTail(2).addToTail(3) 72 | list.removeTail() 73 | 74 | expect(list.size()).to.equal(2) 75 | expect(list.has(1)).to.be.true() 76 | expect(list.has(2)).to.be.true() 77 | expect(list.has(3)).to.be.false() 78 | 79 | const head = new LinkedList().addToHead(1) 80 | head.removeTail() 81 | expect(head.isEmpty()).to.be.true() 82 | }) 83 | 84 | it('.remove()', async () => { 85 | const list = new LinkedList() 86 | list.remove(1).addToHead(1).addToTail(2).addToTail(3) 87 | list.remove(2) 88 | expect(list.size()).to.equal(2) 89 | expect(list.toArray()).to.equal([ 1, 3 ]) 90 | 91 | list.remove(1) 92 | expect(list.toArray()).to.equal([ 3 ]) 93 | 94 | try { 95 | list.remove(10, 2) 96 | expect(true).to.be.false() // this should not be reached 97 | } catch (error) { 98 | expect(error).to.exist() 99 | } 100 | }) 101 | 102 | it('.has()', async () => { 103 | const list = new LinkedList() 104 | list.addToHead(1).addToTail(1) 105 | expect(list.has(1)).to.be.true() 106 | expect(list.has(20)).to.be.false() 107 | }) 108 | 109 | it('.traverse()', async () => { 110 | let sum = 0 111 | const list = new LinkedList() 112 | list.addToTail(1).addToTail(2).addToTail(3) 113 | list.traverse((num) => { 114 | sum = num + sum 115 | }) 116 | expect(sum).to.equal(6) 117 | }) 118 | 119 | it('.toArray()', async () => { 120 | const list = new LinkedList() 121 | list.addToHead(1).addToTail(2).addToTail(3) 122 | expect(list.toArray()).to.equal([ 1, 2, 3 ]) 123 | }) 124 | 125 | it('.size()', async () => { 126 | const list = new LinkedList() 127 | list.addToHead(1).addToHead(2) 128 | expect(list.size()).to.equal(2) 129 | }) 130 | 131 | it('.isEmpty()', async () => { 132 | const list = new LinkedList() 133 | list.addToHead(1) 134 | expect(list.isEmpty()).to.be.false() 135 | 136 | list.removeHead() 137 | expect(list.isEmpty()).to.be.true() 138 | }) 139 | 140 | it('.clear()', async () => { 141 | const list = new LinkedList() 142 | list.addToHead(1) 143 | expect(list.isEmpty()).to.be.false() 144 | 145 | list.clear() 146 | expect(list.isEmpty()).to.be.true() 147 | }) 148 | }) 149 | -------------------------------------------------------------------------------- /test/data-structures/priority-queue.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Lab = require('@hapi/lab') 4 | const { expect } = require('@hapi/code') 5 | const Queue = require('../../data-structures/implementations/priority-queue/priority-queue') 6 | 7 | const { describe, it } = (exports.lab = Lab.script()) 8 | 9 | describe('PriorityQueue', () => { 10 | it('.enqueue()', async () => { 11 | const queue = new Queue() 12 | queue.enqueue(3, 3).enqueue(1, 1) 13 | expect(queue.size()).to.equal(2) 14 | 15 | queue.enqueue(4, 5) 16 | expect(queue.size()).to.equal(3) 17 | expect(queue.peek()).to.equal(1) 18 | 19 | try { 20 | queue.enqueue(1, 0) 21 | expect(true).to.equal(false) // this should not be reached 22 | } catch (error) { 23 | expect(error).to.exist() 24 | } 25 | 26 | try { 27 | queue.enqueue(1, 'priority') 28 | expect(true).to.equal(false) // this should not be reached 29 | } catch (error) { 30 | expect(error).to.exist() 31 | } 32 | }) 33 | 34 | it('.dequeue()', async () => { 35 | const queue = new Queue() 36 | queue.enqueue(3, 3).enqueue(1, 1) 37 | expect(queue.dequeue()).to.equal(1) 38 | expect(queue.dequeue()).to.equal(3) 39 | expect(queue.dequeue()).to.be.undefined() 40 | }) 41 | 42 | it('.peek()', async () => { 43 | const queue = new Queue() 44 | expect(queue.peek()).to.be.undefined() 45 | 46 | queue.enqueue(3, 3).enqueue(1, 1).enqueue(2, 2) 47 | expect(queue.peek()).to.equal(1) 48 | expect(queue.size()).to.equal(3) 49 | }) 50 | 51 | it('.size()', async () => { 52 | const queue = new Queue() 53 | queue.enqueue(1, 1).enqueue(1, 1) 54 | expect(queue.size()).to.equal(2) 55 | }) 56 | 57 | it('.isEmpty()', async () => { 58 | const queue = new Queue() 59 | queue.enqueue(1, 1) 60 | expect(queue.isEmpty()).to.be.false() 61 | 62 | queue.dequeue() 63 | expect(queue.isEmpty()).to.be.true() 64 | }) 65 | 66 | it('.clear()', async () => { 67 | const queue = new Queue() 68 | queue.enqueue(1, 1) 69 | expect(queue.isEmpty()).to.be.false() 70 | 71 | queue.clear() 72 | expect(queue.isEmpty()).to.be.true() 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /test/data-structures/queue.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Lab = require('@hapi/lab') 4 | const { expect } = require('@hapi/code') 5 | const Queue = require('../../data-structures/implementations/queue/queue') 6 | 7 | const { describe, it } = (exports.lab = Lab.script()) 8 | 9 | describe('Queue', () => { 10 | it('.enqueue()', async () => { 11 | const queue = new Queue(1, 2) 12 | queue.enqueue(3) 13 | expect(queue.size()).to.equal(3) 14 | 15 | queue.enqueue(4, 5) 16 | expect(queue.size()).to.equal(5) 17 | expect(queue.peek()).to.equal(1) 18 | }) 19 | 20 | it('.dequeue()', async () => { 21 | const queue = new Queue(1) 22 | expect(queue.dequeue()).to.equal(1) 23 | expect(queue.size()).to.equal(0) 24 | expect(queue.dequeue()).to.be.undefined() 25 | }) 26 | 27 | it('.peek()', async () => { 28 | const queue = new Queue(1, 2, 3) 29 | const item = queue.peek() 30 | expect(item).to.equal(1) 31 | expect(queue.size()).to.equal(3) 32 | }) 33 | 34 | it('.size()', async () => { 35 | const queue = new Queue(1, 2) 36 | expect(queue.size()).to.equal(2) 37 | }) 38 | 39 | it('.isEmpty()', async () => { 40 | const queue = new Queue(1) 41 | expect(queue.isEmpty()).to.be.false() 42 | 43 | queue.dequeue() 44 | expect(queue.isEmpty()).to.be.true() 45 | }) 46 | 47 | it('.clear()', async () => { 48 | const queue = new Queue(1) 49 | expect(queue.isEmpty()).to.be.false() 50 | 51 | queue.clear() 52 | expect(queue.isEmpty()).to.be.true() 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /test/data-structures/set.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Lab = require('@hapi/lab') 4 | const { expect } = require('@hapi/code') 5 | const Set = require('../../data-structures/implementations/set/set') 6 | 7 | const { describe, it } = (exports.lab = Lab.script()) 8 | 9 | describe('Set', () => { 10 | it('.add()', async () => { 11 | const set = new Set(1, 2, 1) 12 | set.add(3) 13 | expect(set.size()).to.equal(3) 14 | 15 | set.add(4, 4, 5) 16 | expect(set.size()).to.equal(5) 17 | }) 18 | 19 | it('.remove()', async () => { 20 | const set = new Set(1, 2) 21 | set.remove(1) 22 | expect(set.size()).to.equal(1) 23 | 24 | set.remove(1) 25 | expect(set.size()).to.equal(1) 26 | }) 27 | 28 | it('.has()', async () => { 29 | const set = new Set(1, 2, 3) 30 | expect(set.has(2)).to.be.true() 31 | expect(set.has(4)).to.be.false() 32 | }) 33 | 34 | it('.toArray()', async () => { 35 | const numbers = new Set(1, 2, 3) 36 | expect(numbers.toArray()).to.equal([1, 2, 3]) 37 | 38 | const strings = new Set('Marcus', 'Norman', 'Christian') 39 | expect(strings.toArray()).to.equal(['Marcus', 'Norman', 'Christian']) 40 | }) 41 | 42 | it('.size()', async () => { 43 | const set = new Set(1, 2, 1, 2) 44 | expect(set.size()).to.equal(2) 45 | }) 46 | 47 | it('.isEmpty()', async () => { 48 | const set = new Set(1) 49 | expect(set.isEmpty()).to.be.false() 50 | 51 | set.remove(1) 52 | expect(set.isEmpty()).to.be.true() 53 | }) 54 | 55 | it('.clear()', async () => { 56 | const set = new Set(1) 57 | expect(set.isEmpty()).to.be.false() 58 | 59 | set.clear() 60 | expect(set.isEmpty()).to.be.true() 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /test/data-structures/stack.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Lab = require('@hapi/lab') 4 | const { expect } = require('@hapi/code') 5 | const Stack = require('../../data-structures/implementations/stack/stack') 6 | 7 | const { describe, it } = (exports.lab = Lab.script()) 8 | 9 | describe('Stack', () => { 10 | it('.push()', async () => { 11 | const stack = new Stack(1, 2) 12 | stack.push(3) 13 | expect(stack.size()).to.equal(3) 14 | 15 | stack.push(4, 5) 16 | expect(stack.size()).to.equal(5) 17 | expect(stack.peek()).to.equal(5) 18 | }) 19 | 20 | it('.pop()', async () => { 21 | const stack = new Stack(1) 22 | expect(stack.pop()).to.equal(1) 23 | expect(stack.size()).to.equal(0) 24 | expect(stack.pop()).to.be.undefined() 25 | }) 26 | 27 | it('.peek()', async () => { 28 | const stack = new Stack(1, 2, 3) 29 | const item = stack.peek() 30 | expect(item).to.equal(3) 31 | expect(stack.size()).to.equal(3) 32 | }) 33 | 34 | it('.size()', async () => { 35 | const stack = new Stack(1, 2) 36 | expect(stack.size()).to.equal(2) 37 | }) 38 | 39 | it('.isEmpty()', async () => { 40 | const stack = new Stack(1) 41 | expect(stack.isEmpty()).to.be.false() 42 | 43 | stack.pop() 44 | expect(stack.isEmpty()).to.be.true() 45 | }) 46 | 47 | it('.clear()', async () => { 48 | const stack = new Stack(1) 49 | expect(stack.isEmpty()).to.be.false() 50 | 51 | stack.clear() 52 | expect(stack.isEmpty()).to.be.true() 53 | }) 54 | }) 55 | --------------------------------------------------------------------------------