├── tslint.json ├── .gitignore ├── .idea └── vcs.xml ├── .editorconfig ├── .travis.yml ├── tsconfig.json ├── src └── await-to-js.ts ├── tools ├── gh-pages-publish.ts └── semantic-release-prepare.ts ├── LICENSE ├── rollup.config.js ├── test └── await-to-js.test.ts ├── examples └── example-1.js ├── README.md └── package.json /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint-config-standard" 4 | ], 5 | "rules": { 6 | "semicolon": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .nyc_output 4 | .DS_Store 5 | *.log 6 | .vscode 7 | .idea 8 | dist 9 | compiled 10 | .awcache -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | #root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | max_line_length = 100 10 | indent_size = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | branches: 3 | only: 4 | - master 5 | - /^greenkeeper/.*$/ 6 | cache: 7 | yarn: true 8 | directories: 9 | - node_modules 10 | notifications: 11 | email: false 12 | node_js: 13 | - node 14 | script: 15 | - npm run test:prod && npm run build 16 | after_success: 17 | - npm run report-coverage 18 | - npm run deploy-docs 19 | - npm run semantic-release 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "es5", 5 | "module":"es2015", 6 | "lib": ["es2015", "es2016", "es2017", "dom"], 7 | "strict": true, 8 | "sourceMap": true, 9 | "declaration": true, 10 | "allowSyntheticDefaultImports": true, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true, 13 | "declarationDir": "dist/types", 14 | "outDir": "compiled", 15 | "typeRoots": [ 16 | "node_modules/@types" 17 | ] 18 | }, 19 | "include": [ 20 | "src" 21 | ] 22 | } -------------------------------------------------------------------------------- /src/await-to-js.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { Promise } promise 3 | * @param { Object= } errorExt - Additional Information you can pass to the err object 4 | * @return { Promise } 5 | */ 6 | export function to ( 7 | promise: Promise, 8 | errorExt?: object 9 | ): Promise<[U, undefined] | [null, T]> { 10 | return promise 11 | .then<[null, T]>((data: T) => [null, data]) 12 | .catch<[U, undefined]>((err: U) => { 13 | if (errorExt) { 14 | const parsedError = Object.assign({}, err, errorExt); 15 | return [parsedError, undefined]; 16 | } 17 | 18 | return [err, undefined]; 19 | }); 20 | } 21 | 22 | export default to; 23 | -------------------------------------------------------------------------------- /tools/gh-pages-publish.ts: -------------------------------------------------------------------------------- 1 | const { cd, exec, echo, touch } = require("shelljs") 2 | const { readFileSync } = require("fs") 3 | const url = require("url") 4 | 5 | let repoUrl 6 | let pkg = JSON.parse(readFileSync("package.json") as any) 7 | if (typeof pkg.repository === "object") { 8 | if (!pkg.repository.hasOwnProperty("url")) { 9 | throw new Error("URL does not exist in repository section") 10 | } 11 | repoUrl = pkg.repository.url 12 | } else { 13 | repoUrl = pkg.repository 14 | } 15 | 16 | let parsedUrl = url.parse(repoUrl) 17 | let repository = (parsedUrl.host || "") + (parsedUrl.path || "") 18 | let ghToken = process.env.GH_TOKEN 19 | 20 | echo("Deploying docs!!!") 21 | cd("dist/docs") 22 | touch(".nojekyll") 23 | exec("git init") 24 | exec("git add .") 25 | exec('git config user.name "Dima Grossman"') 26 | exec('git config user.email "dima@grossman.io"') 27 | exec('git commit -m "docs(docs): update gh-pages"') 28 | exec( 29 | `git push --force --quiet "https://${ghToken}@${repository}" master:gh-pages` 30 | ) 31 | echo("Docs deployed!!") 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Dima Grossman 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from "rollup-plugin-node-resolve" 2 | import commonjs from "rollup-plugin-commonjs" 3 | import sourceMaps from "rollup-plugin-sourcemaps" 4 | const pkg = require("./package.json") 5 | const camelCase = require("lodash.camelcase") 6 | 7 | const libraryName = "await-to-js" 8 | 9 | export default { 10 | input: `compiled/${libraryName}.js`, 11 | output: [ 12 | { file: pkg.main, name: camelCase(libraryName), format: "umd" }, 13 | { file: pkg.module, format: "es" } 14 | ], 15 | sourcemap: true, 16 | // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash') 17 | external: [], 18 | watch: { 19 | include: "compiled/**" 20 | }, 21 | plugins: [ 22 | // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs) 23 | commonjs(), 24 | // Allow node_modules resolution, so you can use 'external' to control 25 | // which external modules to include in the bundle 26 | // https://github.com/rollup/rollup-plugin-node-resolve#usage 27 | resolve(), 28 | 29 | // Resolve source maps to the original source 30 | sourceMaps() 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /test/await-to-js.test.ts: -------------------------------------------------------------------------------- 1 | import { to } from '../src/await-to-js' 2 | 3 | describe('Await to test', async () => { 4 | it('should return a value when resolved', async () => { 5 | const testInput = 41; 6 | const promise = Promise.resolve(testInput); 7 | 8 | const [err, data] = await to(promise); 9 | 10 | expect(err).toBeNull(); 11 | expect(data).toEqual(testInput); 12 | }); 13 | 14 | it('should return an error when promise is rejected', async () => { 15 | const testInput = 41; 16 | const promise = Promise.reject('Error'); 17 | 18 | const [err, data] = await to(promise); 19 | 20 | expect(err).toEqual('Error'); 21 | expect(data).toBeUndefined(); 22 | }); 23 | 24 | it('should add external properties to the error object', async () => { 25 | const promise = Promise.reject({ error: 'Error message' }); 26 | 27 | const [err] = await to< 28 | string, 29 | { error: string; extraKey: number } 30 | >(promise, { 31 | extraKey: 1 32 | }); 33 | 34 | expect(err).toBeTruthy(); 35 | expect((err as any).extraKey).toEqual(1); 36 | expect((err as any).error).toEqual('Error message') 37 | }); 38 | 39 | it('should receive the type of the parent if no type was passed', async () => { 40 | let user: { name: string }; 41 | let err: Error; 42 | 43 | [err, user] = await to(Promise.resolve({ name: '123' })); 44 | 45 | expect(user.name).toEqual('123'); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tools/semantic-release-prepare.ts: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const { fork } = require("child_process") 3 | const colors = require("colors") 4 | 5 | const { readFileSync, writeFileSync } = require("fs") 6 | const pkg = JSON.parse( 7 | readFileSync(path.resolve(__dirname, "..", "package.json")) 8 | ) 9 | 10 | pkg.scripts.prepush = "npm run test:prod && npm run build" 11 | pkg.scripts.commitmsg = "validate-commit-msg" 12 | 13 | writeFileSync( 14 | path.resolve(__dirname, "..", "package.json"), 15 | JSON.stringify(pkg, null, 2) 16 | ) 17 | 18 | // Call husky to set up the hooks 19 | fork(path.resolve(__dirname, "..", "node_modules", "husky", "bin", "install")) 20 | 21 | console.log() 22 | console.log(colors.green("Done!!")) 23 | console.log() 24 | 25 | if (pkg.repository.url.trim()) { 26 | console.log(colors.cyan("Now run:")) 27 | console.log(colors.cyan(" npm install -g semantic-release-cli")) 28 | console.log(colors.cyan(" semantic-release setup")) 29 | console.log() 30 | console.log( 31 | colors.cyan('Important! Answer NO to "Generate travis.yml" question') 32 | ) 33 | console.log() 34 | console.log( 35 | colors.gray( 36 | 'Note: Make sure "repository.url" in your package.json is correct before' 37 | ) 38 | ) 39 | } else { 40 | console.log( 41 | colors.red( 42 | 'First you need to set the "repository.url" property in package.json' 43 | ) 44 | ) 45 | console.log(colors.cyan("Then run:")) 46 | console.log(colors.cyan(" npm install -g semantic-release-cli")) 47 | console.log(colors.cyan(" semantic-release setup")) 48 | console.log() 49 | console.log( 50 | colors.cyan('Important! Answer NO to "Generate travis.yml" question') 51 | ) 52 | } 53 | 54 | console.log() 55 | -------------------------------------------------------------------------------- /examples/example-1.js: -------------------------------------------------------------------------------- 1 | import to from '../../dist'; 2 | 3 | const UserModel = { 4 | findById: (userId) => { 5 | return new Promise((resolve, reject) => { 6 | if(userId) { 7 | const userObjet = { 8 | id: userId, 9 | notificationsEnabled: true 10 | }; 11 | 12 | return resolve(userObjet); 13 | } 14 | 15 | reject('Data is missing'); 16 | }); 17 | } 18 | }; 19 | 20 | const TaskModel = function ({userId, name}) { 21 | return new Promise((resolve, reject) => { 22 | if(userId && name) { 23 | const newTask = { 24 | assignedUser: { 25 | id: userId 26 | } 27 | }; 28 | 29 | return resolve(newTask); 30 | } 31 | 32 | reject('Data is missing'); 33 | }); 34 | }; 35 | 36 | const NotificationService = { 37 | sendNotification: (userId, name) => { 38 | return new Promise((resolve, reject) => { 39 | if(userId && name) return resolve('Success'); 40 | 41 | reject('Data is missing'); 42 | }); 43 | } 44 | }; 45 | 46 | 47 | async function asyncTask(userId, cb) { 48 | let err, user, savedTask, notification; 49 | [ err, user ] = await to(UserModel.findById(userId)); 50 | if(!(user && user.id)) return cb('No user found'); 51 | 52 | [ err, savedTask] = await to(TaskModel({userId: user.id, name: 'Demo Task'})); 53 | if(err) return cb('Error occurred while saving task'); 54 | 55 | if(user.notificationsEnabled) { 56 | [ err ] = await to(NotificationService.sendNotification(user.id, 'Task Created')); 57 | if(err) return cb('Error while sending notification'); 58 | } 59 | 60 | if(savedTask.assignedUser.id !== user.id) { 61 | [ err, notification ] = await to(NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you')); 62 | if(err) return cb('Error while sending notification'); 63 | } 64 | 65 | cb(null, savedTask); 66 | } 67 | 68 | 69 | asyncTask(1, (err, newTask) => { 70 | console.log('new task created'); 71 | console.log(err); 72 | console.log(newTask); 73 | }); 74 | 75 | asyncTask(null, (err, newTask) => { 76 | console.log('fail'); 77 | console.log(err); 78 | console.log(newTask); 79 | }); 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # await-to-js 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![Downloads][download-badge]][npm-url] 5 | 6 | > Async await wrapper for easy error handling 7 | 8 | 9 |
10 | Supported by: 11 |
12 | 13 | 14 | 15 |
16 |
The open-source notification infrastructure 17 |
18 | 19 | ## Pre-requisites 20 | You need to use Node 7.6 (or later) or an ES7 transpiler in order to use async/await functionality. 21 | You can use babel or typescript for that. 22 | 23 | ## Install 24 | 25 | ```sh 26 | npm i await-to-js --save 27 | ``` 28 | 29 | ## Usage 30 | 31 | ```js 32 | import to from 'await-to-js'; 33 | // If you use CommonJS (i.e NodeJS environment), it should be: 34 | // const to = require('await-to-js').default; 35 | 36 | async function asyncTaskWithCb(cb) { 37 | let err, user, savedTask, notification; 38 | 39 | [ err, user ] = await to(UserModel.findById(1)); 40 | if(!user) return cb('No user found'); 41 | 42 | [ err, savedTask ] = await to(TaskModel({userId: user.id, name: 'Demo Task'})); 43 | if(err) return cb('Error occurred while saving task'); 44 | 45 | if(user.notificationsEnabled) { 46 | [ err ] = await to(NotificationService.sendNotification(user.id, 'Task Created')); 47 | if(err) return cb('Error while sending notification'); 48 | } 49 | 50 | if(savedTask.assignedUser.id !== user.id) { 51 | [ err, notification ] = await to(NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you')); 52 | if(err) return cb('Error while sending notification'); 53 | } 54 | 55 | cb(null, savedTask); 56 | } 57 | 58 | async function asyncFunctionWithThrow() { 59 | const [err, user] = await to(UserModel.findById(1)); 60 | if (!user) throw new Error('User not found'); 61 | 62 | } 63 | ``` 64 | 65 | ## TypeScript usage 66 | ```javascript 67 | interface ServerResponse { 68 | test: number; 69 | } 70 | 71 | const p = Promise.resolve({test: 123}); 72 | 73 | const [err, data] = await to(p); 74 | console.log(data.test); 75 | ``` 76 | 77 | 78 | ## License 79 | 80 | MIT © [Dima Grossman](http://blog.grossman.io) && Tomer Barnea 81 | 82 | [npm-url]: https://npmjs.org/package/await-to-js 83 | [npm-image]: https://img.shields.io/npm/v/await-to-js.svg?style=flat-square 84 | 85 | [travis-url]: https://travis-ci.org/scopsy/await-to-js 86 | [travis-image]: https://img.shields.io/travis/scopsy/await-to-js.svg?style=flat-square 87 | 88 | [coveralls-url]: https://coveralls.io/r/scopsy/await-to-js 89 | [coveralls-image]: https://img.shields.io/coveralls/scopsy/await-to-js.svg?style=flat-square 90 | 91 | [depstat-url]: https://david-dm.org/scopsy/await-to-js 92 | [depstat-image]: https://david-dm.org/scopsy/await-to-js.svg?style=flat-square 93 | 94 | [download-badge]: http://img.shields.io/npm/dm/await-to-js.svg?style=flat-square 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "await-to-js", 3 | "version": "3.0.0", 4 | "description": "Async/await wrapper for easy error handling in js", 5 | "keywords": [ 6 | "node", 7 | "async", 8 | "await", 9 | "async await" 10 | ], 11 | "main": "dist/await-to-js.umd.js", 12 | "module": "dist/await-to-js.es5.js", 13 | "typings": "dist/types/await-to-js.d.ts", 14 | "homepage": "https://github.com/scopsy/await-to-js#readme", 15 | "files": [ 16 | "dist" 17 | ], 18 | "author": "Dima Grossman , Tomer Barnea ", 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/scopsy/await-to-js.git" 22 | }, 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">=6.0.0" 26 | }, 27 | "scripts": { 28 | "lint": "tslint -t codeFrame 'src/**/*.ts' 'test/**/*.ts'", 29 | "prebuild": "rimraf dist", 30 | "build": "tsc && rollup -c && rimraf compiled && typedoc --out dist/docs --target es6 --theme minimal src", 31 | "start": "tsc -w & rollup -c -w", 32 | "test": "jest", 33 | "test:watch": "jest --watch", 34 | "test:prod": "npm run lint && npm run test -- --coverage --no-cache", 35 | "deploy-docs": "ts-node tools/gh-pages-publish", 36 | "report-coverage": "cat ./coverage/lcov.info | coveralls", 37 | "commit": "git-cz", 38 | "semantic-release": "semantic-release pre && npm publish && semantic-release post", 39 | "semantic-release-prepare": "ts-node tools/semantic-release-prepare", 40 | "precommit": "lint-staged" 41 | }, 42 | "lint-staged": { 43 | "{src,test}/**/*.ts": [ 44 | "git add" 45 | ] 46 | }, 47 | "config": { 48 | "commitizen": { 49 | "path": "node_modules/cz-conventional-changelog" 50 | }, 51 | "validate-commit-msg": { 52 | "types": "conventional-commit-types", 53 | "helpMessage": "Use \"npm run commit\" instead, we use conventional-changelog format :) (https://github.com/commitizen/cz-cli)" 54 | } 55 | }, 56 | "jest": { 57 | "transform": { 58 | ".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js" 59 | }, 60 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 61 | "moduleFileExtensions": [ 62 | "ts", 63 | "tsx", 64 | "js" 65 | ], 66 | "coveragePathIgnorePatterns": [ 67 | "/node_modules/", 68 | "/test/" 69 | ], 70 | "coverageThreshold": { 71 | "global": { 72 | "branches": 90, 73 | "functions": 95, 74 | "lines": 95, 75 | "statements": 95 76 | } 77 | } 78 | }, 79 | "devDependencies": { 80 | "@types/jest": "^21.1.0", 81 | "@types/node": "^8.0.0", 82 | "colors": "^1.1.2", 83 | "commitizen": "^2.9.6", 84 | "coveralls": "^2.13.1", 85 | "cross-env": "^5.0.1", 86 | "cz-conventional-changelog": "^2.0.0", 87 | "husky": "^0.14.0", 88 | "jest": "^21.0.0", 89 | "lint-staged": "^4.0.0", 90 | "lodash.camelcase": "^4.3.0", 91 | "prompt": "^1.0.0", 92 | "replace-in-file": "^3.0.0-beta.2", 93 | "rimraf": "^2.6.1", 94 | "rollup": "^0.50.0", 95 | "rollup-plugin-commonjs": "^8.0.2", 96 | "rollup-plugin-node-resolve": "^3.0.0", 97 | "rollup-plugin-sourcemaps": "^0.4.2", 98 | "semantic-release": "^8.0.0", 99 | "ts-jest": "^21.0.0", 100 | "ts-node": "^3.0.6", 101 | "tslint": "^5.4.3", 102 | "tslint-config-standard": "^6.0.0", 103 | "typedoc": "^0.8.0", 104 | "typescript": "^2.3.4", 105 | "validate-commit-msg": "^2.12.2" 106 | } 107 | } 108 | --------------------------------------------------------------------------------