T4rk1n/tarkjs

View on GitHub
src/extensions/prom-extensions.js

Summary

Maintainability
C
7 hrs
Test Coverage
/**
* Created by T4rk on 6/19/2017.
*/
 
import { objExtend } from './obj-extensions'
import { globalScope } from '../global-scope'
 
/**
* A promise may be canceled:
* - before it's execution.
* - after it resolves, rejecting instead.
* - during a step of a promise chain, stopping propagation.
* @typedef {Object} CancelablePromise
* @property {Promise} promise
* @property {function} cancel cancel the promise.
*/
 
/**
* @typedef {Object} TError
* @property {!string} error
* @property {?string} message
*/
 
/**
* @typedef {Object} PromiseWrapOptions
* @property {boolean} [rejectNull=false] Reject the return value of the wrapped function if null.
* @property {string} [nullMessage=''] Message to include in the null_result error.
* @property {Array} [args=[]] to give on the function call.
*/
 
/**
* @typedef {Object} PromiseCreator
* @property {function(args:*):Promise} creator
* @property {Array} [promArgs] arguments to call the creator
*/
 
/**
* Create a {@link PromiseCreator} from a function and it's args to be called with.
* @param {function(args:*):Promise} creator
* @param {Array} [args]
* @return {PromiseCreator}
*/
export const promiseCreator = (creator, ...args) => ({
creator,
promArgs: args || []
})
 
/**
* @type {PromiseWrapOptions}
*/
const defaultPromiseWrapOptions = {
rejectNull: null, timeout: null, nullMessage: '', args: []
}
 
 
 
/**
* Wrap a function to resolve the return value and reject errors.
*
* Three callback options may happen:
* - requestIdleCallback chrome >= 47 && opera >= 34
* - setImmediate ie >= 10, Edge, PhantomJS
* - setTimeout(,0) rest
*
* Cancellation before execution if cleared else after.
* @param {function} func the return of the function will be resolved.
* @param {PromiseWrapOptions} [options]
* @return {CancelablePromise}
*/
Function `promiseWrap` has a Cognitive Complexity of 17 (exceeds 5 allowed). Consider refactoring.
Function `promiseWrap` has 39 lines of code (exceeds 25 allowed). Consider refactoring.
export const promiseWrap = (func, options=defaultPromiseWrapOptions) => {
let canceled = false,
cancel = () => {},
reqId = -1
const { rejectNull, nullMessage, args } = objExtend({}, defaultPromiseWrapOptions, options)
Function `promise` has 28 lines of code (exceeds 25 allowed). Consider refactoring.
const promise = new Promise((resolve, reject) => {
const handle = () => {
let result
 
try { result = func(...args) }
catch (e) { return reject(e) }
 
if (canceled) return
 
if (rejectNull && !result) reject({
error:'null_result',
message: nullMessage || 'Expected promise result is null'
})
else {
if (result instanceof Promise) {
result.then((value) => {
if (!canceled) resolve(value)
}).catch(reject)
} else {
resolve(result)
}
}
}
reqId = globalScope.asyncCallback(handle)
cancel = () => {
canceled = true
globalScope.clearCallback(reqId)
reject({
error: 'canceled',
message: `Promise was canceled, reqId = ${reqId}`
})
}
})
 
return {
promise,
cancel,
reqId
}
}
 
/**
* Pre promiseWrap a function to be called later.
* @param {function} func
* @param {Array<*>} args
* @param {PromiseWrapOptions} [options]
* @return {function(): CancelablePromise}
*/
export const wrapLater = (func, args, options={}) => {
return () => promiseWrap(func, {args, ...options})
}
 
/**
* Wrap a promise as cancelable with timeout option.
* Rejection after resolve.
* @param {!Promise} promise
* @param {?int} timeout
* @return {CancelablePromise}
*/
export const toCancelable = (promise, timeout=null) => {
let canceled = false
const dt = new Date()
const wrap = new Promise((resolve, reject) => {
promise.then(value => {
if (canceled) reject({error: 'canceled', message: 'Promise was canceled'})
else if (timeout && new Date() - dt >= timeout)
reject({error: 'time_out', message: `Promise was resolved after timeout of ${timeout}`})
else resolve(value)
}).catch(reject)
})
return {
promise: wrap,
cancel() { canceled = true }
}
}
 
/**
* Executes a function after a delay.
* Cancellation before execution only.
* @param {number} delay
* @param {function} func Executor
* @param {*} fargs arguments given to func call
* @return {CancelablePromise}
*/
export const delayed = (delay, func, ...fargs) => {
let canceled = false
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
if (canceled) reject({
error: 'canceled',
message: 'Delayed execution was canceled'})
else resolve(func(...fargs))
}, delay)
})
return {
promise,
cancel: () => canceled = true
}
}
 
 
/**
* {@link chainPromises} options.
* @typedef {Object} PromiseChainOptions
* @property {function} [onChain] inner promise.then callback.
* @property {function} [onError] inner promise.catch callback.
* @property {boolean} [rejectOnError=true] reject the outer promise on inner rejection.
*/
 
/**
* @type {PromiseChainOptions}
*/
const defaultChainOptions = {
onChain: ()=>{}, onError: ()=>{}, rejectOnError: true,
}
 
/**
* Chains promise until completion or rejection.
* @param {Array<PromiseCreator>} creators
* @param {PromiseChainOptions} [options]
* @return {CancelablePromise}
*/
Function `chainPromises` has 31 lines of code (exceeds 25 allowed). Consider refactoring.
Function `chainPromises` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.
export const chainPromises = (creators, options=defaultChainOptions) => {
let canceled = false, cancel = () => canceled = true
const { onChain, onError, rejectOnError } = objExtend({}, defaultChainOptions, options)
const promise = new Promise((resolve, reject) => {
let i = 0, acc = []
const _err = (err) => {
onError(err)
if (rejectOnError) reject(err)
else chain()
}
const chain = (value) => {
if (value) {
onChain(value)
acc.push(value)
}
if (canceled) reject({
error: 'canceled', message: `Promise chain was canceled after ${i} promise.`
})
else if (i < creators.length) {
const { creator, promArgs } = creators[i]
i++
const prom = creator(...promArgs)
prom.then(chain).catch(_err)
}
else resolve(acc)
}
chain()
})
return {
promise,
cancel
}
}