Skip to content

Instantly share code, notes, and snippets.

@psenger
Last active May 25, 2025 02:01
Show Gist options
  • Save psenger/f630bdfb7f0ea9d28af014e030772a22 to your computer and use it in GitHub Desktop.
Save psenger/f630bdfb7f0ea9d28af014e030772a22 to your computer and use it in GitHub Desktop.
[Design Pattern: Promise All Object] #JavaScript #Promise

🎭 Promise All Object

Because sometimes your promises are buried deeper than your ex's Instagram photos. This utility helps you resolve them all, no matter where they're hiding! 🕵️‍♂️

🌟 Features

  • Resolves promises in deeply nested objects and arrays
  • Works just like Promise.all but for objects (because objects deserve love too!)
  • Handles arrays, objects, and any combination thereof
  • Preserves your object structure (unlike your New Year's resolutions)
  • TypeScript-friendly documentation included! 🎉

🚀 Installation

npm install promise-all-object
# or if you're a yarn person (we don't judge)
yarn add promise-all-object

📖 Usage

Basic Usage

const { promiseAllObject } = require('./promise-tools')

// Simple object with promises
const input = {
  name: Promise.resolve('John'),
  age: Promise.resolve(30)
}

const result = await promiseAllObject(input)
// result = { name: 'John', age: 30 }

Nested Structure (Because Life is Complicated) 🌴

const input = {
  user: Promise.resolve({
    profile: {
      name: Promise.resolve('John'),
      posts: Promise.resolve([
        { title: Promise.resolve('Hello') }
      ])
    }
  })
}

const result = await promiseAllObject(input)
// Everything is resolved! 🎉

Arrays (For the List Lovers) 📝

const input = [Promise.resolve(1), Promise.resolve(2)]
const result = await promiseAllObject(input)
// result = [1, 2]

Error Handling (Because Things Break) 💔

try {
  const input = {
    success: Promise.resolve('yay'),
    failure: Promise.reject(new Error('oops'))
  }
  await promiseAllObject(input)
} catch (error) {
  console.log('Something went wrong! Time to blame the intern.')
}

🧪 Testing

We've got tests! Because we're professionals (most of the time).

# Run the tests
npm test

# Run with coverage (to impress your boss)
npm test -- --coverage

Our tests check:

  • Promise resolution at all depths
  • Array handling
  • Object handling
  • Error propagation
  • Edge cases (null, undefined, etc.)
  • That your code actually works (shocking, right?)

🤓 API

promiseAllObject(input)

Takes any input and resolves all promises within it, no matter how deeply nested they are. Like a treasure hunter, but for promises! 🗺️

isPromise(value)

Checks if something is a Promise. It's like a bouncer for your code, but instead of checking IDs, it checks for .then() methods! 🚪

💡 Pro Tips

  1. Don't nest your promises too deep - even this utility can't save you from callback hell 😈
  2. Remember to use try/catch - promises can break your heart 💔
  3. This utility is recursive - it's promises all the way down! 🐢

🐛 Known Issues

  • Still can't resolve your commitment issues
  • Won't help you keep your promises to go to the gym
  • Doesn't work on promises you made to your mother

📜 License

MIT - Because sharing is caring! ❤️

🤝 Contributing

Pull requests are welcome! Just remember:

  1. Add tests (we're not savages)
  2. Update the docs
  3. Don't break anything
  4. Maybe bring cookies? 🍪

⭐ Show Your Support

If this package saved you from promise hell, consider:

  • Giving it a star ⭐
  • Telling your friends
  • Naming your firstborn after it
/**
* @fileoverview Utilities for working with Promises in deeply nested objects and arrays.
* @module promiseUtils
*/
/**
* Checks if a value is a native Promise instance.
*
* @param {*} value - The value to check.
* @returns {boolean} True if the value is a Promise instance, false otherwise.
*
* @example
* // Returns true
* isPromise(Promise.resolve(42))
* isPromise(new Promise(() => {}))
*
* @example
* // Returns false
* isPromise({ then: () => {} }) // Thenable but not a Promise
* isPromise(null)
* isPromise(42)
* isPromise({ a: Promise.resolve(1) }) // Object containing a promise
*/
const isPromise = (value) => {
return value instanceof Promise
}
/**
* Recursively resolves all promises in an object or array structure.
* Similar to Promise.all but works with objects and deeply nested structures.
*
* @param {*} obj - The input value to process. Can be:
* - A Promise (will be resolved)
* - An object containing promises (all promises will be resolved)
* - An array containing promises (all promises will be resolved)
* - A deeply nested structure of objects and arrays containing promises
* - Any other value (will be returned as-is)
*
* @returns {Promise<*>} A promise that resolves to a new object/array with the same structure
* as the input, but with all promises resolved to their values.
*
* @throws {Error} If any promise in the structure rejects, the entire operation will reject
* with that error.
*
* @example
* // Simple object with promises
* const input = {
* name: Promise.resolve('John'),
* age: Promise.resolve(30)
* }
* const result = await promiseAllObject(input)
* // result = { name: 'John', age: 30 }
*
* @example
* // Deeply nested structure
* const input = {
* user: Promise.resolve({
* profile: {
* name: Promise.resolve('John'),
* posts: Promise.resolve([
* { title: Promise.resolve('Hello') }
* ])
* }
* })
* }
* const result = await promiseAllObject(input)
* // result = {
* // user: {
* // profile: {
* // name: 'John',
* // posts: [{ title: 'Hello' }]
* // }
* // }
* // }
*
* @example
* // Arrays of promises
* const input = [Promise.resolve(1), Promise.resolve(2)]
* const result = await promiseAllObject(input)
* // result = [1, 2]
*
* @example
* // Error handling
* try {
* const input = {
* a: Promise.resolve(1),
* b: Promise.reject(new Error('Failed'))
* }
* await promiseAllObject(input)
* } catch (error) {
* // Error: Failed
* }
*/
const promiseAllObject = async (obj) => {
// If the input is a promise, resolve it and recursively process its value
if (isPromise(obj)) {
return promiseAllObject(await obj)
}
// If the input is an array, recursively process each element
if (Array.isArray(obj)) {
return Promise.all(obj.map(item => promiseAllObject(item)))
}
// If the input is an object, recursively process all its values
if (obj !== null && typeof obj === 'object') {
const keys = Object.keys(obj)
const values = await Promise.all(
keys.map(key => promiseAllObject(obj[key]))
)
return keys.reduce((result, key, index) => {
result[key] = values[index]
return result
}, {})
}
// For all other values (primitives, null, undefined), return as-is
return obj
}
module.exports = {
isPromise,
promiseAllObject
}
/**
* @jest-environment node
*
* Tests for the Promise utilities module.
* - isPromise: Validates Promise instance checking
* - promiseAllObject: Tests deep promise resolution in objects/arrays
*/
const {
isPromise,
promiseAllObject
} = require('./promise-tools')
describe('isPromise', () => {
describe('valid promises', () => {
test('identifies native Promise instances correctly', () => {
expect(isPromise(Promise.resolve())).toBe(true)
expect(isPromise(new Promise(() => {}))).toBe(true)
expect(isPromise(Promise.reject().catch(() => {}))).toBe(true)
})
})
describe('non-promises', () => {
test('correctly identifies non-Promise values', () => {
const nonPromises = [
null,
undefined,
{},
{ then: () => {} }, // thenable but not a Promise
42,
'string',
[],
new Date()
]
nonPromises.forEach(value => {
expect(isPromise(value)).toBe(false)
})
})
})
})
describe('promiseAllObject', () => {
describe('primitive handling', () => {
test('returns primitive values unchanged', async () => {
const primitives = [
42,
'string',
null,
undefined
]
for (const value of primitives) {
expect(await promiseAllObject(value)).toBe(value)
}
})
})
describe('promise resolution', () => {
test('resolves single promise to its value', async () => {
const input = Promise.resolve(42)
expect(await promiseAllObject(input)).toBe(42)
})
test('resolves array of promises', async () => {
const input = [
Promise.resolve(1),
Promise.resolve(2),
3
]
expect(await promiseAllObject(input)).toEqual([1, 2, 3])
})
test('resolves nested promise arrays', async () => {
const input = [
Promise.resolve([
Promise.resolve(1),
Promise.resolve(2)
]),
Promise.resolve(3)
]
expect(await promiseAllObject(input)).toEqual([[1, 2], 3])
})
})
describe('object handling', () => {
test('resolves flat object with promises', async () => {
const input = {
a: Promise.resolve(1),
b: Promise.resolve(2),
c: 3
}
expect(await promiseAllObject(input)).toEqual({
a: 1,
b: 2,
c: 3
})
})
test('resolves deeply nested object structure', async () => {
const input = {
user: Promise.resolve({
name: Promise.resolve('John'),
posts: Promise.resolve([
{
id: 1,
comments: Promise.resolve(['Great!', 'Nice!'])
}
])
})
}
const expected = {
user: {
name: 'John',
posts: [
{
id: 1,
comments: ['Great!', 'Nice!']
}
]
}
}
expect(await promiseAllObject(input)).toEqual(expected)
})
test('handles mixed object and array nesting', async () => {
const input = {
array: [
Promise.resolve({ a: Promise.resolve(1) }),
{ b: Promise.resolve(2) }
],
object: {
c: Promise.resolve([Promise.resolve(3)])
}
}
const expected = {
array: [
{ a: 1 },
{ b: 2 }
],
object: {
c: [3]
}
}
expect(await promiseAllObject(input)).toEqual(expected)
})
})
describe('error handling', () => {
test('rejects for top-level promise rejection', async () => {
const error = new Error('Test error')
const input = {
a: Promise.resolve(1),
b: Promise.reject(error),
c: Promise.resolve(3)
}
await expect(promiseAllObject(input)).rejects.toThrow(error)
})
test('rejects for nested promise rejection', async () => {
const error = new Error('Nested error')
const input = {
user: Promise.resolve({
name: Promise.resolve('John'),
data: Promise.reject(error)
})
}
await expect(promiseAllObject(input)).rejects.toThrow(error)
})
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment