├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── lib └── index.js ├── package-lock.json ├── package.json └── test └── index.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [{package.json,*.yml,*.md}] 12 | indent_style = space 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "eslint:recommended", 4 | "env": { 5 | "node": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 8, 9 | "ecmaFeatures": { 10 | "experimentalObjectRestSpread": true 11 | } 12 | }, 13 | "rules": { 14 | "no-undef": 2, 15 | "no-unused-vars": 2, 16 | "eqeqeq": 2, 17 | "quotes": [ 18 | 0, 19 | "single" 20 | ], 21 | "strict": 0, 22 | "no-use-before-define": 0, 23 | "curly": [ 24 | 2, 25 | "multi-line" 26 | ], 27 | "no-shadow": 0, 28 | "new-cap": 0, 29 | "eol-last": 0, 30 | "no-underscore-dangle": 0, 31 | "no-console": 2, 32 | "space-before-function-paren": [ 33 | 2, 34 | { 35 | "anonymous": "always", 36 | "named": "never" 37 | } 38 | ], 39 | "space-before-blocks": [ 40 | 2, 41 | { 42 | "functions": "always", 43 | "keywords": "always", 44 | "classes": "always" 45 | } 46 | ], 47 | "keyword-spacing": [ 48 | 2, 49 | { 50 | "before": true, 51 | "after": true 52 | } 53 | ] 54 | }, 55 | "globals": { 56 | "Promise": true, 57 | "HttpError": true 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Build 5 | dist 6 | 7 | # Logs 8 | npm-debug.log 9 | 10 | # Coverage 11 | coverage 12 | .nyc_output 13 | 14 | # Editors 15 | .vscode 16 | .idea 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "10" 5 | after_success: 6 | - npm run coverage 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2018 MONO JS 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 |

Mono Utils

2 | 3 | > Node.js utils to deal with async/await. 4 | 5 | [![npm version](https://img.shields.io/npm/v/mono-utils.svg)](https://www.npmjs.com/package/mono-utils) 6 | [![Travis](https://img.shields.io/travis/mono-js/mono-utils/master.svg)](https://travis-ci.org/mono-js/mono-utils) 7 | [![Coverage](https://img.shields.io/codecov/c/github/mono-js/mono-utils/master.svg)](https://codecov.io/mono-js/mono-utils) 8 | [![license](https://img.shields.io/github/license/mono-js/mono-utils.svg)](https://github.com/mono-js/mono-utils/blob/master/LICENSE) 9 | 10 | 11 | ## Installation 12 | 13 | 14 | ```bash 15 | npm install --save mono-utils 16 | ``` 17 | 18 | ## Usage 19 | 20 | **INFO:** You need `node` >= `8.0.0` to use mono-utils since it uses native `async/await` 21 | 22 | ```js 23 | const { ok, cb, waitFor, ... } = require('mono-utils') 24 | ``` 25 | 26 | ## Utils 27 | 28 | - [ok](#ok) 29 | - [cb](#cb) 30 | - [waitFor](#waitfor) 31 | - [waitForEvent](#waitforevent) 32 | - [asyncObject](#asyncobject) 33 | - [asyncMap](#asyncmap) 34 | - [asyncForEach](#asyncforeach) 35 | 36 | ### ok 37 | 38 | Waits for the value of the `Promise` and returns its value. If the promise throws an `Error`, returns `undefined`. 39 | 40 | ```js 41 | ok(promise: Object): Promise 42 | ``` 43 | 44 | Example: 45 | 46 | ```js 47 | const { ok } = require('mono-core/utils') 48 | const { readFile } = require('fs-extra') 49 | 50 | // readFile sends back a Promise since we use fs-extra 51 | const file = await ok(readFile('./my-file.txt', 'utf-8')) 52 | 53 | if (file) console.log('File found:', file) 54 | ``` 55 | 56 | ### cb 57 | 58 | Calls a function Node style function as first argument `function (err, result)`, all the others arguments will be given to the function. Waits for the callback result, throws an `Error` if `err` is truthy. 59 | 60 | ```js 61 | cb(fn: Function, ...args: any[]): Promise 62 | ``` 63 | 64 | Example: 65 | 66 | ```js 67 | const { ok } = require('mono-core/utils') 68 | const fs = require('fs') 69 | 70 | try { 71 | const file = await cb(fs.readFile, '/path/to/my/file.txt', 'utf-8') 72 | } catch (err) { 73 | // Could not read file 74 | } 75 | ``` 76 | 77 | ### waitFor 78 | 79 | Waits for `ms` milliseconds to pass, use `setTimeout` under the hood. 80 | 81 | ```js 82 | waitFor(ms: number): Promise 83 | ``` 84 | 85 | Example: 86 | 87 | ```js 88 | const { waitFor } = require('mono-core/utils') 89 | 90 | await waitFor(1000) // wait for 1s 91 | ``` 92 | 93 | ### waitForEvent 94 | 95 | Waits for emitter to emit an eventName event. 96 | 97 | ```js 98 | waitForEvent(emitter: EventEmitter, eventName: string, timeout: number = -1): Promise 99 | ``` 100 | 101 | Example: 102 | 103 | ```js 104 | const { waitFor } = require('mono-core/utils') 105 | 106 | await waitForEvent(sever, 'listen') 107 | ``` 108 | 109 | ### asyncObject 110 | 111 | Waits for all Promises in the keys of obj to resolve. 112 | 113 | ```js 114 | asyncObject(obj: Object): Promise 115 | ``` 116 | 117 | Example: 118 | 119 | ```js 120 | const { asyncObject } = require('mono-core/utils') 121 | 122 | const { pictures, comments, tweets } = await asyncObject({ 123 | pictures: getPictures(), 124 | comments: getComments(), 125 | tweets: getTweets() 126 | }) 127 | 128 | console.log(pictures, comments, tweets) 129 | ``` 130 | 131 | ### asyncMap 132 | 133 | Waits for all Promises mapped by `fn`: 134 | 135 | ```js 136 | asyncMap(array: Array, fn: Function): Promise 137 | ``` 138 | 139 | Example: 140 | 141 | ```js 142 | const { asyncMap } = require('mono-core/utils') 143 | 144 | const posts = await asyncMap([1, 2, 3], (id) => fetchPost(id)) 145 | ``` 146 | 147 | ### asyncForEach 148 | 149 | Loop for every item in array and call `fn` and wait for it to finish **in series**: 150 | 151 | ```js 152 | asyncForEach(array: Array, fn: Function): Promise 153 | ``` 154 | 155 | Example: 156 | 157 | ```js 158 | const { asyncMap } = require('mono-core/utils') 159 | 160 | const posts = await asyncForEach(users, async (user) => { 161 | await saveUser(user) 162 | }) 163 | ``` 164 | 165 | ## Other utils 166 | 167 | We developed other utils that you might find useful: 168 | 169 | - [mongodb-utils](https://github.com/mono-js/mongodb-utils) 170 | - [elasticsearch-utils](https://github.com/mono-js/elasticsearch-utils) 171 | - [mono-test-utils](https://github.com/mono-js/mono-test-utils) 172 | 173 | ## LICENSE 174 | 175 | [MIT](https://github.com/mono-js/mono-utils/blob/master/LICENSE) 176 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** ok(promise: Promise): 3 | */ 4 | exports.ok = async function (promise) { 5 | try { 6 | return await promise 7 | } catch (err) { 8 | return 9 | } 10 | } 11 | 12 | /* 13 | ** cb(fn: Function, args: ...any): 14 | */ 15 | exports.cb = function (fn, ...args) { 16 | return new Promise((resolve, reject) => { 17 | fn(...args, (err, result) => { 18 | if (err) return reject(err) 19 | resolve(result) 20 | }) 21 | }) 22 | } 23 | 24 | /* 25 | ** waitFor(ms) 26 | */ 27 | exports.waitFor = function (ms) { 28 | return new Promise((resolve) => setTimeout(resolve, ms || 0)) 29 | } 30 | 31 | /* 32 | ** waitForEvent(emmiter, eventName) 33 | */ 34 | exports.waitForEvent = function (emmiter, eventName, timeout = -1) { 35 | return new Promise((resolve, reject) => { 36 | emmiter.once(eventName, (...args) => resolve([...args])) 37 | if (timeout >= 0) setTimeout(() => reject(new Error(`Wait for event timeout (${timeout}ms)`)), timeout) 38 | }) 39 | } 40 | 41 | /* 42 | ** asyncObject(obj): Promise 43 | */ 44 | exports.asyncObject = async function (obj = {}) { 45 | const containsPromise = (key) => obj[key] && typeof obj[key].then === 'function' 46 | const keys = Object.keys(obj).filter(containsPromise) 47 | const promises = keys.map((key) => obj[key]) 48 | const results = await Promise.all(promises) 49 | const container = Object.assign({}, obj) 50 | results.forEach((result, index) => { 51 | const key = keys[index] 52 | container[key] = result 53 | }) 54 | return container 55 | } 56 | 57 | /* 58 | ** asyncMap(array, fn): Promise 59 | */ 60 | exports.asyncMap = async function (array, fn) { 61 | return await Promise.all(array.map(fn)) 62 | } 63 | 64 | /* 65 | ** asyncForEach(array, fn): Promise 66 | */ 67 | exports.asyncForEach = async function (array, fn) { 68 | for (let i = 0; i < array.length; i++) { 69 | await fn(array[i], i, array) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mono-utils", 3 | "version": "1.0.0", 4 | "description": "Node.js utils to deal with async/await", 5 | "main": "lib/", 6 | "files": [ 7 | "lib" 8 | ], 9 | "nyc": { 10 | "include": [ 11 | "lib/" 12 | ] 13 | }, 14 | "engines": { 15 | "node": ">=8.0.0" 16 | }, 17 | "scripts": { 18 | "lint": "eslint lib/ test/", 19 | "test": "npm run lint && npm run t", 20 | "t": "nyc ava --verbose --serial ---fail-fast test/ && nyc report --reporter=html", 21 | "coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov", 22 | "preversion": "npm test" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/mono-js/mono-utils.git" 27 | }, 28 | "contributors": [ 29 | { 30 | "name": "Sebastien Chopin (@Atinux)" 31 | }, 32 | { 33 | "name": "Benjamin Canac (@benjamincanac)" 34 | } 35 | ], 36 | "license": "MIT", 37 | "devDependencies": { 38 | "ava": "0.25.0", 39 | "codecov": "3.0.2", 40 | "eslint": "4.19.1", 41 | "nyc": "11.7.3" 42 | }, 43 | "keywords": [ 44 | "mono", 45 | "async await", 46 | "async utils", 47 | "mono utils", 48 | "utils", 49 | "await utils", 50 | "tools" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | const EventEmitter = require('events').EventEmitter 3 | 4 | const { asyncObject, waitFor, cb, ok, waitForEvent, asyncMap, asyncForEach } = require('..') 5 | 6 | async function p(val, delay = 0) { 7 | await waitFor(delay) 8 | return val 9 | } 10 | 11 | async function fail(delay = 0) { 12 | await waitFor(delay) 13 | throw new Error('fail') 14 | } 15 | 16 | /* 17 | ** asyncObject 18 | */ 19 | test('Returns the results of promises + non-promises', async (t) => { 20 | const res = await asyncObject({ 21 | foo: p('foo'), 22 | bar: p('bar'), 23 | baz: p('baz'), 24 | n: null, 25 | u: undefined 26 | }) 27 | const expected = { foo: 'foo', bar: 'bar', baz: 'baz', n: null, u: undefined } 28 | t.deepEqual(res, expected) 29 | }) 30 | 31 | test('Returns empty object with no parameter', async (t) => { 32 | const obj = await asyncObject() 33 | t.deepEqual(obj, {}) 34 | }) 35 | 36 | test('Throws an error is one promise throws', async (t) => { 37 | const err = await t.throws(asyncObject({ 38 | foo: fail(100), 39 | bar: p('bar'), 40 | fail: fail() 41 | })) 42 | t.is(err.message, 'fail') 43 | }) 44 | 45 | /* 46 | ** cb 47 | */ 48 | function read(file, callback) { 49 | if (file === 'foo.txt') { 50 | return setTimeout(() => callback(null, 'contents'), 0) 51 | } 52 | setTimeout(() => callback(new Error('file not found')), 0) 53 | } 54 | 55 | test('Passes args and receives results', async (t) => { 56 | const result = await cb(read, 'foo.txt') 57 | t.is(result, 'contents') 58 | }) 59 | 60 | test('Throws an error if (err) is truthy', async (t) => { 61 | const err = await t.throws(cb(read, 'bar.txt')) 62 | t.is(err.message, 'file not found') 63 | }) 64 | 65 | /* 66 | ** ok 67 | */ 68 | test('Returns a value on success', async (t) => { 69 | const foo = await ok(p('foo')) 70 | t.is(foo, 'foo') 71 | }) 72 | 73 | test('Returns undefined on error', async (t) => { 74 | const res = await ok(fail()) 75 | t.is(res, undefined) 76 | }) 77 | 78 | /* 79 | ** waitForEvent 80 | */ 81 | function newEmitter(eventName, ms, ...args) { 82 | const emitter = new EventEmitter() 83 | setTimeout(() => { 84 | emitter.emit(eventName, ...args) 85 | }, ms) 86 | 87 | return emitter 88 | } 89 | 90 | test('Should wait until event is emitted', async (t) => { 91 | const emitter = newEmitter('listen', 100) 92 | const start = Date.now() 93 | await waitForEvent(emitter, 'listen') 94 | t.true(Date.now() - start >= 90) 95 | }) 96 | 97 | test('Should work without parameter', async (t) => { 98 | const emitter = newEmitter('close', 10, 'foo', 123) 99 | const res = await waitForEvent(emitter, 'close') 100 | t.deepEqual(res, ['foo', 123]) 101 | }) 102 | 103 | test('Should trigger timeout', async (t) => { 104 | const emitter = new EventEmitter() 105 | const error = await t.throws(waitForEvent(emitter, 'close', 100)) 106 | t.true(error.message.includes('Wait for event timeout (100ms)')) 107 | }) 108 | 109 | /* 110 | ** waitFor 111 | */ 112 | test('Should wait for 100ms', async (t) => { 113 | const start = Date.now() 114 | await waitFor(100) 115 | t.true(Date.now() - start >= 100) 116 | }) 117 | 118 | test('Should work without parameter', async (t) => { 119 | const start = Date.now() 120 | await waitFor() 121 | t.true(Date.now() - start <= 30) 122 | }) 123 | 124 | /* 125 | ** asyncMap 126 | */ 127 | test('Returns the results of promises + non-promises', async (t) => { 128 | const res = await asyncMap(['foo', 'bar', 'baz', null, undefined], (doc) => p(doc)) 129 | const expected = ['foo', 'bar', 'baz', null, undefined] 130 | t.deepEqual(res, expected) 131 | }) 132 | 133 | /* 134 | ** asyncForEach 135 | */ 136 | test('Returns the results of promises + non-promises', async (t) => { 137 | const array = ['foo', 'bar', 'baz', null, undefined] 138 | const res = {} 139 | await asyncForEach(array, async (value) => { 140 | await waitFor(10) 141 | res[String(value)] = value 142 | }) 143 | const expected = { 144 | 'foo': 'foo', 145 | 'bar': 'bar', 146 | 'baz': 'baz', 147 | 'null': null, 148 | 'undefined': undefined 149 | } 150 | t.deepEqual(res, expected) 151 | }) 152 | --------------------------------------------------------------------------------