@yukiakai/promise-scope
v1.0.1
Published
A Promise with built-in lifecycle control (timeout, abort, cleanup)
Maintainers
Readme
@yukiakai/promise-scope
A Promise with built-in lifecycle control (timeout, abort, cleanup)
Features
- Built-in timeout
- AbortSignal support
- Automatic cleanup via DisposableStack
- Scoped lifecycle (no leaks)
- Promise-compatible (
await,.then) - Strict mode (no silent failures)
- Safe async executor handling (no unhandled rejections)
Installation
npm install @yukiakai/promise-scopeUsage
import { ScopedPromise } from "@yukiakai/promise-scope"
await new ScopedPromise((resolve, reject, { signal, stack }) => {
const timer = setInterval(() => console.log("tick"), 1000)
stack.defer(() => clearInterval(timer))
fetch(url, { signal })
.then(r => r.json())
.then(resolve, reject)
}, { timeout: 5000 })Abort Example
const controller = new AbortController()
await new ScopedPromise((resolve, reject, { signal }) => {
fetch(url, { signal }).then(resolve, reject)
}, { signal: controller.signal })
controller.abort()Cleanup Example
await new ScopedPromise((resolve, reject, { stack }) => {
const resource = createResource()
stack.defer(() => resource.dispose())
doWork(resource).then(resolve, reject)
})API
new ScopedPromise(executor, options)
executor
(resolve, reject, ctx) => void | Promise<void>ctx
signal: AbortSignalstack: ScopedStack(DisposableStack withoutdispose())
options
timeout?: numbersignal?: AbortSignal
Errors
AbortedError— when aborted (including timeout)TimeoutError— available ascauseofAbortedErrorAlreadySettledError— thrown ifresolve/rejectis called more than once
Strict Behavior
ScopedPromise is strict by design:
- Calling
resolveorrejectmore than once will throw an uncaughtAlreadySettledError - Calling
abortmore than once will throw an error - Unlike native
Promise, repeated calls are not silently ignored
This helps detect logic bugs early instead of hiding them.
Async Executor Behavior
Unlike native Promise, async executors are handled safely:
new ScopedPromise(async () => {
throw new Error("boom") // handled, no unhandledRejection
})Errors thrown inside async executors are automatically forwarded to reject.
Notes
- Uses
@yukiakai/disposable-stackfor deterministic and strict cleanup semantics - Cleanup is always executed after resolve/reject
- Timeout triggers abort internally
- External
AbortSignalis supported stack.dispose()is intentionally not exposed — lifecycle is managed internally- Side effects inside
resolve(...)arguments are evaluated before callingresolve(JavaScript behavior)
Requirements
- Node.js 18+ recommended
- Modern environments with AbortController support
License
MIT — Yuki Akai
