Skip to content

Instantly share code, notes, and snippets.

@tjconcept
Last active September 13, 2022 22:16
Show Gist options
  • Save tjconcept/69994c78c8f5b8008258353292787519 to your computer and use it in GitHub Desktop.
Save tjconcept/69994c78c8f5b8008258353292787519 to your computer and use it in GitHub Desktop.
"join" with deterministic behavior for rejections
class Rejection {
constructor(reason) {
this.reason = reason
}
}
export default function using(...args) {
const lastIdx = args.length - 1
const fn = args[lastIdx]
if (typeof fn !== 'function') {
throw new Error('Missing expected function argument')
}
if (lastIdx === 0) {
throw new Error('At least two arguments must be passed')
}
const wrapped = Array(lastIdx)
for (let i = 0; i < lastIdx; i++) {
wrapped[i] = args[i].catch((err) => new Rejection(err))
}
return Promise.all(wrapped).then((results) => {
const err = results.find((r) => r instanceof Rejection)
if (err !== undefined) {
return Promise.reject(err.reason)
} else {
return fn(...results)
}
})
}
@tjconcept
Copy link
Author

I guess it is also kind of a problem.. You would be silencing any potential error from latter promises.

@addic
Copy link

addic commented Jul 15, 2021

I guess it is also kind of a problem.. You would be silencing any potential error from latter promises.

Hehe, not an issue with my beast :D

@tjconcept
Copy link
Author

I fixed it, I think.

The only deficiency, that could be fixed, would be to "reject earlier". That is, if a value rejects, and all values to the left of it has resolved, reject straight away.
However, if the primary use case is servers, the "common case" would be for all values to resolve, and then this is as optimal as it gets. I think it would be way more complicated to "reject early" too.

Just like the original join (from Bluebird) and the native Promise.all, there's the trade-off that you must accept silencing all other rejections. Only in super edge cases is that an issue, but imagine a bug that occurs in your second parameter only when the first one also rejects, e.g. a syntax error. You'd never find it and it could lead to a memory leak or data corruption:

const a = Promise.reject(new Error('Oops!'))
const b = a.then(
	() => 'Hurra!',
	(err) => FallbackPlan()
)
join(a, b, console.log)

You'll never see that FallbackPlan is not defined, or worse if it fails half-way and doesn't clean up and leaks.

So, if you're the pedantic type, you'd probably not use join or Promise.all if exceptions are part of your flow control for operational loads. All code can do that, but it may be too cumbersome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment