
Ask for Promise
Decouple a
Promisefrom itsresolve/rejectand let any code complete it.
A standard Promise is born inside an executor function, and only that function can resolve or reject it. ask-for-promise flips that around — it returns a Promise together with its resolve and reject handles as plain object properties, so you can call them from anywhere: a callback, an event handler, a timer, another promise, or a sync function.
Why
The moment an async task finishes is rarely the same place where the task was started. ask-for-promise is a small DX layer for those cases:
- Race conditions — share one
donebetween N parallel callbacks to emulatePromise.race. - Long chains — declare each step as a named
askForPromise()and wire them up without nesting.then. - Mixed sync / async — a function that's sometimes sync can still call
task.done(). - Legacy callback APIs — pass
task.donestraight to a Node-style callback. - Timeouts — race any promise against a TTL with
.timeout(ttl, fallback).
Features
- Decoupled
done/cancel— call them from anywhere onCompletesugar forpromise.then, with an optional reject handler.timeout(ttl, fallback)— race against a timeraskForPromise(list)— oneAskObjectfor N promises, with.eachfor iterationaskForPromise.sequence(list, ...args)— run steps in order, threading results as argsaskForPromise.all(list, ...args)— run steps in parallel- TypeScript types included
- Zero runtime dependencies
Installation
npm install ask-for-promise
// ES modules
import askForPromise from 'ask-for-promise'
// CommonJS
const askForPromise = require('ask-for-promise')
Quick start
import askForPromise from 'ask-for-promise'
const task = askForPromise()
// Call `done` from anywhere — even a callback API:
setTimeout(() => task.done('done'), 1000)
// `onComplete` is sugar for `task.promise.then`
task.onComplete((result) => {
console.log(result) // → 'done'
})
task.promise is a real Promise. task.done is its resolve. task.cancel is its reject. Anything you can do with a normal Promise works here too.
API
askForPromise() → AskObject
Creates a single promise and returns its AskObject.
const task = askForPromise()
askForPromise(list) → AskObject
Creates one promise per item in list and returns a single AskObject that controls them all. task.promise resolves to an array of values in input order when every sub-promise resolves.
const task = askForPromise(['a', 'b', 'c'])
askForPromise.sequence(list, ...args) → AskObject
Runs each function in list one after the other. The result of each function is appended to args and passed to the next — useful for piping values through async steps.
const steps = [loadUser, loadPosts, renderPage]
askForPromise.sequence(steps, userId).onComplete(([user, posts, html]) => {
// ...
})
askForPromise.all(list, ...args) → AskObject
Runs each entry in list in parallel. Entries can be functions (called with ...args) or already-running promises; the result array is in declaration order.
askForPromise.all([fetchA, fetchB, fetchC], token).onComplete(([a, b, c]) => {
// ...
})
AskObject
| Property | Description |
|---|---|
promise |
The underlying Promise. In list mode this is Promise.all(promises). |
promises |
AskObject[] in list mode, null in single mode. Use task.promises[i].done(value) to resolve a single item. |
done(value?) |
Resolves the promise(s) with value. In list mode, resolves every sub-promise with the same value. |
cancel(reason?) |
Rejects the promise(s) with reason. In list mode, rejects every sub-promise with the same reason. |
onComplete(resolveFn, rejectFn?) |
Sugar for promise.then. Pass a second function as the reject handler. |
each(cbFn, ...args) |
Calls cbFn({ value, done, cancel, timeout }, index, ...args) per item — index is the position in the input list. |
timeout(ttl, fallback) |
Arms a ttl-millisecond timer. On expiry, the task settles with fallback: onComplete is rewired to return the fallback, and the underlying task.promise is settled with the same value (so await task.promise and task.onComplete(...) agree). In list mode, each still-pending sub-promise is replaced with fallback while sub-promises that already settled keep their real value. Returns the same AskObject so you can keep chaining. |
Examples
Simple promise
const task = askForPromise()
function asyncWork() {
// ... do something
task.done('task complete')
}
task.onComplete((result) => {
console.log(result) // → 'task complete'
})
Promise.race, the manual way
A single shared done produces a race — the first callback to call it wins, the rest are no-ops.
const task = askForPromise()
slowAsync((err, r) => task.done('slow'))
fastAsync((err, r) => task.done('fast'))
task.onComplete((winner) => {
console.log(winner) // → 'fast'
})
Long chain without nesting
Declare each step as a named variable, then wire them up:
const prepareFolders = askForPromise()
const writeFiles = askForPromise()
const updateInterface = askForPromise()
myFS.makeFolders(folders, () => prepareFolders.done())
prepareFolders
.onComplete(() => {
myFS.writeFiles(files, () => writeFiles.done())
return writeFiles.promise
})
.then(() => {
updateInterface() // sync part of the work
updateInterface.done()
return updateInterface.promise
})
.then(() => {
console.log('DONE')
})
Promise with timeout
timeout returns the same AskObject, so you can keep chaining.
const task = askForPromise().timeout(2000, 'expire')
setTimeout(() => task.done('success'), 5000)
task.onComplete((result) => {
if (result === 'expire') console.log('timed out')
else console.log(result)
})
In list mode, the timer applies to the whole group:
const task = askForPromise([job1, job2, job3]).timeout(1000, 'expire')
List of promises with .each
task.each(cb) walks the list and hands you a per-item { value, done, cancel, timeout } plus the item's index:
const files = ['info.txt', 'general.txt', 'about.txt']
const task = askForPromise(files)
task.each(({ value, done, cancel }, index) => {
console.log(`writing ${value} (#${index})`)
fs.writeFile(value, 'dummy text', (err) => {
if (err) cancel(err)
else done()
})
})
task.onComplete(() => console.log('DONE'))
task.promise here is equivalent to Promise.all(task.promises.map(p => p.promise)). To resolve a single item rather than all of them, call task.promises[i].done(value).
TypeScript
Type definitions are bundled — no extra install.
import askForPromise, { AskObject } from 'ask-for-promise'
const task: AskObject = askForPromise()
License
MIT — see LICENSE.
Credits
Created by Peter Naydenov.