first commit
This commit is contained in:
3
libraries/promise-utils/.gitignore
vendored
Normal file
3
libraries/promise-utils/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
# managed by monorepo$ bin/update_build_scripts
|
||||
.npmrc
|
5
libraries/promise-utils/.mocharc.json
Normal file
5
libraries/promise-utils/.mocharc.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"ui": "bdd",
|
||||
"recursive": "true",
|
||||
"reporter": "spec"
|
||||
}
|
1
libraries/promise-utils/.nvmrc
Normal file
1
libraries/promise-utils/.nvmrc
Normal file
@@ -0,0 +1 @@
|
||||
20.18.2
|
10
libraries/promise-utils/buildscript.txt
Normal file
10
libraries/promise-utils/buildscript.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
promise-utils
|
||||
--dependencies=None
|
||||
--docker-repos=gcr.io/overleaf-ops
|
||||
--env-add=
|
||||
--env-pass-through=
|
||||
--esmock-loader=False
|
||||
--is-library=True
|
||||
--node-version=20.18.2
|
||||
--public-repo=False
|
||||
--script-version=4.7.0
|
283
libraries/promise-utils/index.js
Normal file
283
libraries/promise-utils/index.js
Normal file
@@ -0,0 +1,283 @@
|
||||
const { promisify, callbackify } = require('node:util')
|
||||
const pLimit = require('p-limit')
|
||||
|
||||
module.exports = {
|
||||
promisify,
|
||||
promisifyAll,
|
||||
promisifyClass,
|
||||
promisifyMultiResult,
|
||||
callbackify,
|
||||
callbackifyAll,
|
||||
callbackifyClass,
|
||||
callbackifyMultiResult,
|
||||
expressify,
|
||||
expressifyErrorHandler,
|
||||
promiseMapWithLimit,
|
||||
promiseMapSettledWithLimit,
|
||||
}
|
||||
|
||||
/**
|
||||
* Promisify all functions in a module.
|
||||
*
|
||||
* This is meant to be used only when all functions in the module are async
|
||||
* callback-style functions.
|
||||
*
|
||||
* It's very much tailored to our current module structure. In particular, it
|
||||
* binds `this` to the module when calling the function in order not to break
|
||||
* modules that call sibling functions using `this`.
|
||||
*
|
||||
* This will not magically fix all modules. Special cases should be promisified
|
||||
* manually.
|
||||
*
|
||||
* The second argument is a bag of options:
|
||||
*
|
||||
* - without: an array of function names that shouldn't be promisified
|
||||
*
|
||||
* - multiResult: an object whose keys are function names and values are lists
|
||||
* of parameter names. This is meant for functions that invoke their callbacks
|
||||
* with more than one result in separate parameters. The promisifed function
|
||||
* will return these results as a single object, with each result keyed under
|
||||
* the corresponding parameter name.
|
||||
*/
|
||||
function promisifyAll(module, opts = {}) {
|
||||
const { without = [], multiResult = {} } = opts
|
||||
const promises = {}
|
||||
for (const propName of Object.getOwnPropertyNames(module)) {
|
||||
if (without.includes(propName)) {
|
||||
continue
|
||||
}
|
||||
const propValue = module[propName]
|
||||
if (typeof propValue !== 'function') {
|
||||
continue
|
||||
}
|
||||
if (multiResult[propName] != null) {
|
||||
promises[propName] = promisifyMultiResult(
|
||||
propValue,
|
||||
multiResult[propName]
|
||||
).bind(module)
|
||||
} else {
|
||||
promises[propName] = promisify(propValue).bind(module)
|
||||
}
|
||||
}
|
||||
return promises
|
||||
}
|
||||
|
||||
/**
|
||||
* Promisify all methods in a class.
|
||||
*
|
||||
* Options are the same as for promisifyAll
|
||||
*/
|
||||
function promisifyClass(cls, opts = {}) {
|
||||
const promisified = class extends cls {}
|
||||
const { without = [], multiResult = {} } = opts
|
||||
for (const propName of Object.getOwnPropertyNames(cls.prototype)) {
|
||||
if (propName === 'constructor' || without.includes(propName)) {
|
||||
continue
|
||||
}
|
||||
const propValue = cls.prototype[propName]
|
||||
if (typeof propValue !== 'function') {
|
||||
continue
|
||||
}
|
||||
if (multiResult[propName] != null) {
|
||||
promisified.prototype[propName] = promisifyMultiResult(
|
||||
propValue,
|
||||
multiResult[propName]
|
||||
)
|
||||
} else {
|
||||
promisified.prototype[propName] = promisify(propValue)
|
||||
}
|
||||
}
|
||||
return promisified
|
||||
}
|
||||
|
||||
/**
|
||||
* Promisify a function that returns multiple results via additional callback
|
||||
* parameters.
|
||||
*
|
||||
* The promisified function returns the results in a single object whose keys
|
||||
* are the names given in the array `resultNames`.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* function f(callback) {
|
||||
* return callback(null, 1, 2, 3)
|
||||
* }
|
||||
*
|
||||
* const g = promisifyMultiResult(f, ['a', 'b', 'c'])
|
||||
*
|
||||
* const result = await g() // returns {a: 1, b: 2, c: 3}
|
||||
*/
|
||||
function promisifyMultiResult(fn, resultNames) {
|
||||
function promisified(...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
fn.bind(this)(...args, (err, ...results) => {
|
||||
if (err != null) {
|
||||
return reject(err)
|
||||
}
|
||||
const promiseResult = {}
|
||||
for (let i = 0; i < resultNames.length; i++) {
|
||||
promiseResult[resultNames[i]] = results[i]
|
||||
}
|
||||
resolve(promiseResult)
|
||||
})
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
return promisified
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse of `promisifyAll`.
|
||||
*
|
||||
* Callbackify all async functions in a module and return them in an object. In
|
||||
* contrast with `promisifyAll`, all other exports from the module are added to
|
||||
* the result.
|
||||
*
|
||||
* This is meant to be used like this:
|
||||
*
|
||||
* const MyPromisifiedModule = {...}
|
||||
* module.exports = {
|
||||
* ...callbackifyAll(MyPromisifiedModule),
|
||||
* promises: MyPromisifiedModule
|
||||
* }
|
||||
*
|
||||
* @param {Object} module - The module to callbackify
|
||||
* @param {Object} opts - Options
|
||||
* @param {Array<string>} opts.without - Array of method names to exclude from
|
||||
* being callbackified
|
||||
* @param {Object} opts.multiResult - Spec of methods to be callbackified with
|
||||
* callbackifyMultiResult()
|
||||
*/
|
||||
function callbackifyAll(module, opts = {}) {
|
||||
const { without = [], multiResult = {} } = opts
|
||||
const callbacks = {}
|
||||
for (const propName of Object.getOwnPropertyNames(module)) {
|
||||
if (without.includes(propName)) {
|
||||
continue
|
||||
}
|
||||
const propValue = module[propName]
|
||||
if (typeof propValue === 'function') {
|
||||
if (propValue.constructor.name === 'AsyncFunction') {
|
||||
if (multiResult[propName] != null) {
|
||||
callbacks[propName] = callbackifyMultiResult(
|
||||
propValue,
|
||||
multiResult[propName]
|
||||
).bind(module)
|
||||
} else {
|
||||
callbacks[propName] = callbackify(propValue).bind(module)
|
||||
}
|
||||
} else {
|
||||
callbacks[propName] = propValue.bind(module)
|
||||
}
|
||||
} else {
|
||||
callbacks[propName] = propValue
|
||||
}
|
||||
}
|
||||
return callbacks
|
||||
}
|
||||
|
||||
/**
|
||||
* Callbackify all methods in a class.
|
||||
*
|
||||
* Options are the same as for callbackifyAll
|
||||
*/
|
||||
function callbackifyClass(cls, opts = {}) {
|
||||
const callbackified = class extends cls {}
|
||||
const { without = [], multiResult = {} } = opts
|
||||
for (const propName of Object.getOwnPropertyNames(cls.prototype)) {
|
||||
if (propName === 'constructor' || without.includes(propName)) {
|
||||
continue
|
||||
}
|
||||
const propValue = cls.prototype[propName]
|
||||
if (typeof propValue !== 'function') {
|
||||
continue
|
||||
}
|
||||
if (multiResult[propName] != null) {
|
||||
callbackified.prototype[propName] = callbackifyMultiResult(
|
||||
propValue,
|
||||
multiResult[propName]
|
||||
)
|
||||
} else {
|
||||
callbackified.prototype[propName] = callbackify(propValue)
|
||||
}
|
||||
}
|
||||
return callbackified
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the effect of `promisifyMultiResult`.
|
||||
*
|
||||
* This is meant for providing a temporary backward compatible callback
|
||||
* interface while we migrate to promises.
|
||||
*/
|
||||
function callbackifyMultiResult(fn, resultNames) {
|
||||
function callbackified(...args) {
|
||||
const [callback] = args.splice(-1)
|
||||
fn.apply(this, args)
|
||||
.then(result => {
|
||||
const cbResults = resultNames.map(resultName => result[resultName])
|
||||
callback(null, ...cbResults)
|
||||
})
|
||||
.catch(err => {
|
||||
callback(err)
|
||||
})
|
||||
}
|
||||
return callbackified
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an async function into an Express middleware
|
||||
*
|
||||
* Any error will be passed to the error middlewares via `next()`
|
||||
*/
|
||||
function expressify(fn) {
|
||||
return (req, res, next) => {
|
||||
return fn(req, res, next).catch(next)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an async function into an Error Handling Express middleware
|
||||
*
|
||||
* Any error will be passed to the error middlewares via `next()`
|
||||
*/
|
||||
function expressifyErrorHandler(fn) {
|
||||
return (err, req, res, next) => {
|
||||
fn(err, req, res, next).catch(next)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map values in `array` with the async function `fn`
|
||||
*
|
||||
* Limit the number of unresolved promises to `concurrency`.
|
||||
* @template T
|
||||
* @template V
|
||||
* @param {number} concurrency
|
||||
* @param {Array<T>} array
|
||||
* @param {(arg: T) => Promise<V>} fn
|
||||
* @return {Promise<Array<Awaited<V>>>}
|
||||
*/
|
||||
async function promiseMapWithLimit(concurrency, array, fn) {
|
||||
const limit = pLimit(concurrency)
|
||||
return await Promise.all(array.map(x => limit(() => fn(x))))
|
||||
}
|
||||
|
||||
/**
|
||||
* Map values in `array` with the async function `fn`
|
||||
*
|
||||
* Limit the number of unresolved promises to `concurrency`.
|
||||
*
|
||||
* @template T, U
|
||||
* @param {number} concurrency
|
||||
* @param {Array<T>} array
|
||||
* @param {(T) => Promise<U>} fn
|
||||
* @return {Promise<Array<PromiseSettledResult<U>>>}
|
||||
*/
|
||||
function promiseMapSettledWithLimit(concurrency, array, fn) {
|
||||
const limit = pLimit(concurrency)
|
||||
return Promise.allSettled(array.map(x => limit(() => fn(x))))
|
||||
}
|
27
libraries/promise-utils/package.json
Normal file
27
libraries/promise-utils/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@overleaf/promise-utils",
|
||||
"version": "0.1.0",
|
||||
"description": "utilities to help working with promises",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "npm run lint && npm run format && npm run types:check && npm run test:unit",
|
||||
"test:unit": "mocha --exit test/**/*.{js,cjs}",
|
||||
"lint": "eslint --ext .js --ext .cjs --ext .ts --max-warnings 0 --format unix .",
|
||||
"lint:fix": "eslint --fix --ext .js --ext .cjs --ext .ts .",
|
||||
"format": "prettier --list-different $PWD/'**/*.{js,cjs,ts}'",
|
||||
"format:fix": "prettier --write $PWD/'**/*.{js,cjs,ts}'",
|
||||
"test:ci": "npm run test:unit",
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
"author": "Overleaf (https://www.overleaf.com)",
|
||||
"license": "AGPL-3.0-only",
|
||||
"devDependencies": {
|
||||
"chai": "^4.3.10",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"mocha": "^11.1.0",
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"p-limit": "^2.3.0"
|
||||
}
|
||||
}
|
4
libraries/promise-utils/test/setup.js
Normal file
4
libraries/promise-utils/test/setup.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const chai = require('chai')
|
||||
const chaiAsPromised = require('chai-as-promised')
|
||||
|
||||
chai.use(chaiAsPromised)
|
451
libraries/promise-utils/test/unit/PromiseUtilsTests.js
Normal file
451
libraries/promise-utils/test/unit/PromiseUtilsTests.js
Normal file
@@ -0,0 +1,451 @@
|
||||
const { expect } = require('chai')
|
||||
const {
|
||||
promisifyAll,
|
||||
promisifyClass,
|
||||
callbackifyMultiResult,
|
||||
callbackifyClass,
|
||||
callbackifyAll,
|
||||
expressify,
|
||||
expressifyErrorHandler,
|
||||
} = require('../..')
|
||||
|
||||
describe('promisifyAll', function () {
|
||||
describe('basic functionality', function () {
|
||||
before(function () {
|
||||
this.module = {
|
||||
SOME_CONSTANT: 1,
|
||||
asyncAdd(a, b, callback) {
|
||||
callback(null, a + b)
|
||||
},
|
||||
asyncDouble(x, callback) {
|
||||
this.asyncAdd(x, x, callback)
|
||||
},
|
||||
}
|
||||
this.promisified = promisifyAll(this.module)
|
||||
})
|
||||
|
||||
it('promisifies functions in the module', async function () {
|
||||
const sum = await this.promisified.asyncAdd(29, 33)
|
||||
expect(sum).to.equal(62)
|
||||
})
|
||||
|
||||
it('binds this to the original module', async function () {
|
||||
const sum = await this.promisified.asyncDouble(38)
|
||||
expect(sum).to.equal(76)
|
||||
})
|
||||
|
||||
it('does not copy over non-functions', async function () {
|
||||
expect(this.promisified).not.to.have.property('SOME_CONSTANT')
|
||||
})
|
||||
|
||||
it('does not modify the prototype of the module', async function () {
|
||||
expect(this.promisified.toString()).to.equal('[object Object]')
|
||||
})
|
||||
})
|
||||
|
||||
describe('without option', function () {
|
||||
before(function () {
|
||||
this.module = {
|
||||
asyncAdd(a, b, callback) {
|
||||
callback(null, a + b)
|
||||
},
|
||||
syncAdd(a, b) {
|
||||
return a + b
|
||||
},
|
||||
}
|
||||
this.promisified = promisifyAll(this.module, { without: ['syncAdd'] })
|
||||
})
|
||||
|
||||
it('does not promisify excluded functions', function () {
|
||||
expect(this.promisified.syncAdd).not.to.exist
|
||||
})
|
||||
|
||||
it('promisifies other functions', async function () {
|
||||
const sum = await this.promisified.asyncAdd(12, 89)
|
||||
expect(sum).to.equal(101)
|
||||
})
|
||||
})
|
||||
|
||||
describe('multiResult option', function () {
|
||||
before(function () {
|
||||
this.module = {
|
||||
asyncAdd(a, b, callback) {
|
||||
callback(null, a + b)
|
||||
},
|
||||
asyncArithmetic(a, b, callback) {
|
||||
callback(null, a + b, a * b)
|
||||
},
|
||||
}
|
||||
this.promisified = promisifyAll(this.module, {
|
||||
multiResult: { asyncArithmetic: ['sum', 'product'] },
|
||||
})
|
||||
})
|
||||
|
||||
it('promisifies multi-result functions', async function () {
|
||||
const result = await this.promisified.asyncArithmetic(3, 6)
|
||||
expect(result).to.deep.equal({ sum: 9, product: 18 })
|
||||
})
|
||||
|
||||
it('promisifies other functions normally', async function () {
|
||||
const sum = await this.promisified.asyncAdd(6, 1)
|
||||
expect(sum).to.equal(7)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('promisifyClass', function () {
|
||||
describe('basic functionality', function () {
|
||||
before(function () {
|
||||
this.Class = class {
|
||||
constructor(a) {
|
||||
this.a = a
|
||||
}
|
||||
|
||||
asyncAdd(b, callback) {
|
||||
callback(null, this.a + b)
|
||||
}
|
||||
}
|
||||
this.Promisified = promisifyClass(this.Class)
|
||||
})
|
||||
|
||||
it('promisifies the class methods', async function () {
|
||||
const adder = new this.Promisified(1)
|
||||
const sum = await adder.asyncAdd(2)
|
||||
expect(sum).to.equal(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('without option', function () {
|
||||
before(function () {
|
||||
this.Class = class {
|
||||
constructor(a) {
|
||||
this.a = a
|
||||
}
|
||||
|
||||
asyncAdd(b, callback) {
|
||||
callback(null, this.a + b)
|
||||
}
|
||||
|
||||
syncAdd(b) {
|
||||
return this.a + b
|
||||
}
|
||||
}
|
||||
this.Promisified = promisifyClass(this.Class, { without: ['syncAdd'] })
|
||||
})
|
||||
|
||||
it('does not promisify excluded functions', function () {
|
||||
const adder = new this.Promisified(10)
|
||||
const sum = adder.syncAdd(12)
|
||||
expect(sum).to.equal(22)
|
||||
})
|
||||
|
||||
it('promisifies other functions', async function () {
|
||||
const adder = new this.Promisified(23)
|
||||
const sum = await adder.asyncAdd(3)
|
||||
expect(sum).to.equal(26)
|
||||
})
|
||||
})
|
||||
|
||||
describe('multiResult option', function () {
|
||||
before(function () {
|
||||
this.Class = class {
|
||||
constructor(a) {
|
||||
this.a = a
|
||||
}
|
||||
|
||||
asyncAdd(b, callback) {
|
||||
callback(null, this.a + b)
|
||||
}
|
||||
|
||||
asyncArithmetic(b, callback) {
|
||||
callback(null, this.a + b, this.a * b)
|
||||
}
|
||||
}
|
||||
this.Promisified = promisifyClass(this.Class, {
|
||||
multiResult: { asyncArithmetic: ['sum', 'product'] },
|
||||
})
|
||||
})
|
||||
|
||||
it('promisifies multi-result functions', async function () {
|
||||
const adder = new this.Promisified(3)
|
||||
const result = await adder.asyncArithmetic(6)
|
||||
expect(result).to.deep.equal({ sum: 9, product: 18 })
|
||||
})
|
||||
|
||||
it('promisifies other functions normally', async function () {
|
||||
const adder = new this.Promisified(6)
|
||||
const sum = await adder.asyncAdd(1)
|
||||
expect(sum).to.equal(7)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('callbackifyMultiResult', function () {
|
||||
it('callbackifies a multi-result function', function (done) {
|
||||
async function asyncArithmetic(a, b) {
|
||||
return { sum: a + b, product: a * b }
|
||||
}
|
||||
const callbackified = callbackifyMultiResult(asyncArithmetic, [
|
||||
'sum',
|
||||
'product',
|
||||
])
|
||||
callbackified(3, 11, (err, sum, product) => {
|
||||
if (err != null) {
|
||||
return done(err)
|
||||
}
|
||||
expect(sum).to.equal(14)
|
||||
expect(product).to.equal(33)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('propagates errors', function (done) {
|
||||
async function asyncBomb() {
|
||||
throw new Error('BOOM!')
|
||||
}
|
||||
const callbackified = callbackifyMultiResult(asyncBomb, [
|
||||
'explosives',
|
||||
'dynamite',
|
||||
])
|
||||
callbackified(err => {
|
||||
expect(err).to.exist
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('callbackifyAll', function () {
|
||||
describe('basic functionality', function () {
|
||||
before(function () {
|
||||
this.module = {
|
||||
SOME_CONSTANT: 1,
|
||||
async asyncAdd(a, b) {
|
||||
return a + b
|
||||
},
|
||||
async asyncDouble(x, callback) {
|
||||
return await this.asyncAdd(x, x)
|
||||
},
|
||||
dashConcat(a, b) {
|
||||
return `${a}-${b}`
|
||||
},
|
||||
}
|
||||
this.callbackified = callbackifyAll(this.module)
|
||||
})
|
||||
|
||||
it('callbackifies async functions in the module', function (done) {
|
||||
this.callbackified.asyncAdd(77, 18, (err, sum) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(sum).to.equal(95)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('binds this to the original module', function (done) {
|
||||
this.callbackified.asyncDouble(20, (err, double) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(double).to.equal(40)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('copies over regular functions', function () {
|
||||
const s = this.callbackified.dashConcat('ping', 'pong')
|
||||
expect(s).to.equal('ping-pong')
|
||||
})
|
||||
|
||||
it('copies over non-functions', function () {
|
||||
expect(this.callbackified.SOME_CONSTANT).to.equal(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('multiResult option', function () {
|
||||
before(function () {
|
||||
this.module = {
|
||||
async asyncAdd(a, b) {
|
||||
return a + b
|
||||
},
|
||||
async asyncArithmetic(a, b) {
|
||||
return { sum: a + b, product: a * b }
|
||||
},
|
||||
}
|
||||
this.callbackified = callbackifyAll(this.module, {
|
||||
multiResult: { asyncArithmetic: ['sum', 'product'] },
|
||||
})
|
||||
})
|
||||
|
||||
it('callbackifies multi-result functions', function (done) {
|
||||
this.callbackified.asyncArithmetic(4, 5, (err, sum, product) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(sum).to.equal(9)
|
||||
expect(product).to.equal(20)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('callbackifies other functions normally', function (done) {
|
||||
this.callbackified.asyncAdd(77, 18, (err, sum) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(sum).to.equal(95)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('without option', function () {
|
||||
before(function () {
|
||||
this.module = {
|
||||
async asyncAdd(a, b) {
|
||||
return a + b
|
||||
},
|
||||
async asyncArithmetic(a, b) {
|
||||
return { sum: a + b, product: a * b }
|
||||
},
|
||||
}
|
||||
this.callbackified = callbackifyAll(this.module, {
|
||||
without: ['asyncAdd'],
|
||||
})
|
||||
})
|
||||
|
||||
it('does not callbackify excluded functions', function () {
|
||||
expect(this.callbackified.asyncAdd).not.to.exist
|
||||
})
|
||||
|
||||
it('callbackifies other functions', async function () {
|
||||
this.callbackified.asyncArithmetic(5, 6, (err, { sum, product }) => {
|
||||
expect(err).not.to.exist
|
||||
expect(sum).to.equal(11)
|
||||
expect(product).to.equal(30)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('callbackifyClass', function () {
|
||||
describe('basic functionality', function () {
|
||||
before(function () {
|
||||
this.Class = class {
|
||||
constructor(a) {
|
||||
this.a = a
|
||||
}
|
||||
|
||||
async asyncAdd(b) {
|
||||
return this.a + b
|
||||
}
|
||||
}
|
||||
this.Callbackified = callbackifyClass(this.Class)
|
||||
})
|
||||
|
||||
it('callbackifies the class methods', function (done) {
|
||||
const adder = new this.Callbackified(1)
|
||||
adder.asyncAdd(2, (err, sum) => {
|
||||
expect(err).not.to.exist
|
||||
expect(sum).to.equal(3)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('without option', function () {
|
||||
before(function () {
|
||||
this.Class = class {
|
||||
constructor(a) {
|
||||
this.a = a
|
||||
}
|
||||
|
||||
async asyncAdd(b) {
|
||||
return this.a + b
|
||||
}
|
||||
|
||||
syncAdd(b) {
|
||||
return this.a + b
|
||||
}
|
||||
}
|
||||
this.Callbackified = callbackifyClass(this.Class, {
|
||||
without: ['syncAdd'],
|
||||
})
|
||||
})
|
||||
|
||||
it('does not callbackify excluded functions', function () {
|
||||
const adder = new this.Callbackified(10)
|
||||
const sum = adder.syncAdd(12)
|
||||
expect(sum).to.equal(22)
|
||||
})
|
||||
|
||||
it('callbackifies other functions', function (done) {
|
||||
const adder = new this.Callbackified(1)
|
||||
adder.asyncAdd(2, (err, sum) => {
|
||||
expect(err).not.to.exist
|
||||
expect(sum).to.equal(3)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('multiResult option', function () {
|
||||
before(function () {
|
||||
this.Class = class {
|
||||
constructor(a) {
|
||||
this.a = a
|
||||
}
|
||||
|
||||
async asyncAdd(b) {
|
||||
return this.a + b
|
||||
}
|
||||
|
||||
async asyncArithmetic(b) {
|
||||
return { sum: this.a + b, product: this.a * b }
|
||||
}
|
||||
}
|
||||
this.Callbackified = callbackifyClass(this.Class, {
|
||||
multiResult: { asyncArithmetic: ['sum', 'product'] },
|
||||
})
|
||||
})
|
||||
|
||||
it('callbackifies multi-result functions', function (done) {
|
||||
const adder = new this.Callbackified(3)
|
||||
adder.asyncArithmetic(6, (err, sum, product) => {
|
||||
expect(err).not.to.exist
|
||||
expect(sum).to.equal(9)
|
||||
expect(product).to.equal(18)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('callbackifies other functions normally', function (done) {
|
||||
const adder = new this.Callbackified(6)
|
||||
adder.asyncAdd(2, (err, sum) => {
|
||||
expect(err).not.to.exist
|
||||
expect(sum).to.equal(8)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('expressify', function () {
|
||||
it('should propagate any rejection to the "next" callback', function (done) {
|
||||
const fn = () => Promise.reject(new Error('rejected'))
|
||||
expressify(fn)({}, {}, error => {
|
||||
expect(error.message).to.equal('rejected')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('expressifyErrorHandler', function () {
|
||||
it('should propagate any rejection to the "next" callback', function (done) {
|
||||
const fn = () => Promise.reject(new Error('rejected'))
|
||||
expressifyErrorHandler(fn)({}, {}, {}, error => {
|
||||
expect(error.message).to.equal('rejected')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
7
libraries/promise-utils/tsconfig.json
Normal file
7
libraries/promise-utils/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.backend.json",
|
||||
"include": [
|
||||
"**/*.js",
|
||||
"**/*.cjs"
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user