├── .eslintrc.json ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── index.js ├── jsdoc2md └── README.hbs ├── package.json ├── src ├── cached.js ├── index.js ├── promiseproxy.js └── util.js └── test ├── promiseproxy.js └── util.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "globals": { 7 | "chrome": true 8 | }, 9 | "extends": "eslint:recommended", 10 | "rules": { 11 | "indent": [ 12 | "error", 13 | 2 14 | ], 15 | "linebreak-style": [ 16 | "error", 17 | "unix" 18 | ], 19 | "quotes": [ 20 | "error", 21 | "single" 22 | ], 23 | "semi": [ 24 | "error", 25 | "never" 26 | ], 27 | "no-console": 0, 28 | "no-undef": 0, 29 | "no-unused-vars": "warn", 30 | "comma-dangle": ["error", "always-multiline"] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /jsdoc2md 2 | .gitignore 3 | .eslintrc.json 4 | /test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2015 slikts 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the “Software”), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![view on npm](http://img.shields.io/npm/v/promiseproxy.svg)][package-url] 2 | [![License][license-image]][license-url] 3 | 4 | # Lightweight promisified API wrappers 5 | A yet another library for *promisifying* callback-style APIs, but this time implemented using the ES2015 [`Proxy`][1] object. It works by intercepting method calls to the API and returning a promise if a callback parameter was expected. 6 | 7 | The benefit of using proxies is that the API is extended without the need to duplicate or mutate the original API implementation. The main functionality of the proxies is implemented in less than [20 lines][8], making this approach lightweight and easily auditable. 8 | 9 | ## Used in 10 | 11 | * [**promiseproxy-chrome**][3] – Promisified [Chrome extension API][5] 12 | * [**promiseproxy-node**][2] – Promisified [Node.js 6.x API][4] 13 | 14 | ## About `Proxy` 15 | 16 | * [Exploring ES6 – Metaprogramming with proxies][7] 17 | * [You Don't Know JS – Chapter 7: Meta Programming][6] 18 | 19 | ## Requirements 20 | 21 | `Proxy` requires native ES2015 support since it's not practicable to shim it for ES5 environments. It is supported in Node.js 6+, Chrome, Firefox and Edge. 22 | 23 | * [Browser support for `Proxy`](https://kangax.github.io/compat-table/es6/#test-Proxy) 24 | * [Node.js support for `Proxy`](http://node.green/#Proxy) 25 | 26 | ## API 27 | **Example** 28 | ```js 29 | const {PromiseProxy} = require("promiseproxy") 30 | ``` 31 | 32 | 33 | ### PromiseProxy(target, schema) ⇒ Proxy ⏏ 34 | Factory of [`Proxy`][1] objects for recursively promisifying a callback-based API 35 | 36 | **Kind**: global method of [promiseproxy](#module_promiseproxy) 37 | 38 | | Param | Type | Description | 39 | | --- | --- | --- | 40 | | target | Object | The API to be promisifed | 41 | | schema | Object | API structure with callback parameter position | 42 | 43 | **Example** 44 | ```js 45 | // Define chrome.tabs.query(_, callback) and .update(_, _, callback) methods 46 | // 1 and 2 are the positions of the callback parameters (zero-based) 47 | const schema = {tabs: {query: 1, update: 2}} 48 | // Promisify the Chrome API based on the schema 49 | const _chrome = PromiseProxy(chrome, schema) 50 | // The promisified methods return a Promise if the callback parameter is omitted 51 | _chrome.tabs.query(info).then(callback) 52 | // The same methods can still be used with a callback 53 | _chrome.tabs.query(info, callback) 54 | ``` 55 | 56 | [1]: https://goo.gl/ICTTFQ 57 | [2]: https://github.com/slikts/promiseproxy-node 58 | [3]: https://github.com/slikts/promiseproxy-chrome 59 | [4]: https://nodejs.org/api/ 60 | [5]: https://developer.chrome.com/extensions/api_index 61 | [6]: https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20%26%20beyond/ch7.md#proxies 62 | [7]: http://exploringjs.com/es6/ch_proxies.html 63 | [8]: src/promiseproxy.js#L22-L40 64 | [package-url]: https://npmjs.com/package/promiseproxy 65 | [npm-badge-png]: https://nodei.co/npm/promiseproxy.png 66 | [license-url]: LICENSE 67 | [license-image]: http://img.shields.io/npm/l/promiseproxy.svg 68 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const {CachedPromiseProxy} = require('./src') 2 | 3 | module.exports = { 4 | PromiseProxy: CachedPromiseProxy, 5 | } 6 | -------------------------------------------------------------------------------- /jsdoc2md/README.hbs: -------------------------------------------------------------------------------- 1 | [![view on npm](http://img.shields.io/npm/v/promiseproxy.svg)][package-url] 2 | [![License][license-image]][license-url] 3 | 4 | # Lightweight promisified API wrappers 5 | A yet another library for *promisifying* callback-style APIs, but this time implemented using the ES2015 [`Proxy`][1] object. It works by intercepting method calls to the API and returning a promise if a callback parameter was expected. 6 | 7 | The benefit of using proxies is that the API is extended without the need to duplicate or mutate the original API implementation. The main functionality of the proxies is implemented in less than [20 lines][8], making this approach lightweight and easily auditable. 8 | 9 | ## Used in 10 | 11 | * [**promiseproxy-chrome**][3] – Promisified [Chrome extension API][5] 12 | * [**promiseproxy-node**][2] – Promisified [Node.js 6.x API][4] 13 | 14 | ## About `Proxy` 15 | 16 | * [Exploring ES6 – Metaprogramming with proxies][7] 17 | * [You Don't Know JS – Chapter 7: Meta Programming][6] 18 | 19 | ## Requirements 20 | 21 | `Proxy` requires native ES2015 support since it's not practicable to shim it for ES5 environments. It is supported in Node.js 6+, Chrome, Firefox and Edge. 22 | 23 | * [Browser support for `Proxy`](https://kangax.github.io/compat-table/es6/#test-Proxy) 24 | * [Node.js support for `Proxy`](http://node.green/#Proxy) 25 | 26 | ## API 27 | {{#module name='promiseproxy'~}} 28 | {{>body~}} 29 | {{>member-index~}} 30 | {{>members~}} 31 | {{/module}} 32 | 33 | [1]: https://goo.gl/ICTTFQ 34 | [2]: https://github.com/slikts/promiseproxy-node 35 | [3]: https://github.com/slikts/promiseproxy-chrome 36 | [4]: https://nodejs.org/api/ 37 | [5]: https://developer.chrome.com/extensions/api_index 38 | [6]: https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20%26%20beyond/ch7.md#proxies 39 | [7]: http://exploringjs.com/es6/ch_proxies.html 40 | [8]: src/promiseproxy.js#L22-L40 41 | [package-url]: https://npmjs.com/package/promiseproxy 42 | [npm-badge-png]: https://nodei.co/npm/promiseproxy.png 43 | [license-url]: LICENSE 44 | [license-image]: http://img.shields.io/npm/l/promiseproxy.svg 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "promiseproxy", 3 | "version": "1.0.3", 4 | "description": "Recursively promisify callback-style APIs based on a simple scheme and Proxy objects", 5 | "main": "prome.js", 6 | "scripts": { 7 | "test": "tape test/*.js", 8 | "docs": "jsdoc2md -t jsdoc2md/README.hbs src/*.js > README.md" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+ssh://git@github.com/slikts/promiseproxy.git" 13 | }, 14 | "keywords": [ 15 | "promise", 16 | "promisify", 17 | "callback", 18 | "cps", 19 | "deep", 20 | "recursive", 21 | "proxy" 22 | ], 23 | "author": "slikts ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/slikts/promiseproxy/issues" 27 | }, 28 | "homepage": "https://github.com/slikts/promiseproxy#readme", 29 | "devDependencies": { 30 | "jsdoc-to-markdown": "^1.3.6", 31 | "tape": "^4.5.1" 32 | }, 33 | "engines": { 34 | "node": ">= 6.0.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/cached.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const {PromiseProxy} = require('./promiseproxy') 4 | 5 | /** Wraps PromiseProxy factory and caches instances in a WeakMap */ 6 | function CachedPromiseProxy(target, context, cache = new WeakMap(), factory = PromiseProxy) { 7 | const cached = cache.get(target) 8 | if (cached) { 9 | return cached 10 | } 11 | const obj = factory(target, context, 12 | (target, context) => CachedPromiseProxy(target, context, cache)) 13 | cache.set(target, obj) 14 | return obj 15 | } 16 | 17 | module.exports = {CachedPromiseProxy} 18 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const {PromiseProxy} = require('./promiseproxy') 2 | const {CachedPromiseProxy} = require('./cached') 3 | 4 | module.exports = {PromiseProxy, CachedPromiseProxy} 5 | -------------------------------------------------------------------------------- /src/promiseproxy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const {insert} = require('./util') 4 | 5 | /** 6 | * Factory of [`Proxy`][1] objects for recursively promisifying a callback-based API 7 | * @param {Object} target The API to be promisifed 8 | * @param {Object} schema API structure with callback parameter position 9 | * @return {Proxy} 10 | * @alias module:promiseproxy 11 | * @example 12 | * // Define chrome.tabs.query(_, callback) and .update(_, _, callback) methods 13 | * // 1 and 2 are the positions of the callback parameters (zero-based) 14 | * const schema = {tabs: {query: 1, update: 2}} 15 | * // Promisify the Chrome API based on the schema 16 | * const _chrome = PromiseProxy(chrome, schema) 17 | * // The promisified methods return a Promise if the callback parameter is omitted 18 | * _chrome.tabs.query(info).then(callback) 19 | * // The same methods can still be used with a callback 20 | * _chrome.tabs.query(info, callback) 21 | */ 22 | function PromiseProxy(target, schema, self = PromiseProxy) { 23 | const handler = { 24 | apply(method, receiver, args) { 25 | const index = schema 26 | if (args[index] != null) { 27 | return Reflect.apply(method, receiver, args) 28 | } 29 | return new Promise(resolve => Reflect.apply(method, receiver, insert(args, resolve, index))) 30 | }, 31 | get(target, key) { 32 | const prop = target[key] 33 | if (schema.hasOwnProperty(key)) { 34 | return self(prop, schema[key]) 35 | } 36 | return prop 37 | }, 38 | } 39 | return new Proxy(target, handler) 40 | } 41 | 42 | /** 43 | * @module promiseproxy 44 | * @example 45 | * const {PromiseProxy} = require("promiseproxy") 46 | */ 47 | module.exports = {PromiseProxy} 48 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | insert: (arr, item, pos) => arr.slice(0, pos).concat([item], arr.slice(pos)), 5 | } 6 | -------------------------------------------------------------------------------- /test/promiseproxy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('tape') 4 | const {PromiseProxy} = require('../') 5 | 6 | test('instatiate: with new', function (t) { 7 | const schema = { 8 | tabs: { 9 | query: 1, 10 | }, 11 | } 12 | 13 | const n = 123 14 | // mock 15 | const _chrome = { 16 | tabs: { 17 | query: (_, callback) => { 18 | setTimeout(() => callback(n), 0) 19 | }, 20 | }, 21 | } 22 | 23 | const pchrome = PromiseProxy(_chrome, schema) 24 | 25 | pchrome.tabs.query({a: 1}).then((arg) => { 26 | t.equal(arg, n) 27 | t.end() 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const {insert} = require('../src/util') 3 | 4 | test('replace', function (t) { 5 | t.deepEqual(insert([1,2,3], 5, 1), [1,5,2,3]) 6 | t.end() 7 | }) 8 | --------------------------------------------------------------------------------