diff --git a/src/index.ts b/src/index.ts index ac1003d..4c14a1a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import * as clipboardItem from './clipboarditem.js' import * as elementCheckVisibility from './element-checkvisibility.js' import * as navigatorClipboard from './navigator-clipboard.js' +import * as withResolvers from './promise-withResolvers.js' import * as requestIdleCallback from './requestidlecallback.js' let supportsModalPseudo = false @@ -47,6 +48,7 @@ export const polyfills = { elementCheckVisibility, navigatorClipboard, requestIdleCallback, + withResolvers, } export function isSupported() { diff --git a/src/promise-withResolvers.ts b/src/promise-withResolvers.ts new file mode 100644 index 0000000..04be228 --- /dev/null +++ b/src/promise-withResolvers.ts @@ -0,0 +1,30 @@ +/*#__PURE__*/ +export function withResolvers(this: PromiseConstructor) { + const out = {} as { + promise: Promise + resolve: (value: T | PromiseLike) => void + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reject: (reason?: any) => void + } + out.promise = new Promise((resolve, reject) => { + out.resolve = resolve + out.reject = reject + }) + return out +} + +/*#__PURE__*/ +export function isSupported(): boolean { + return 'withResolvers' in Promise && typeof Promise.withResolvers === 'function' +} + +/*#__PURE__*/ +export function isPolyfilled(): boolean { + return 'withResolvers' in Promise && Promise.withResolvers === withResolvers +} + +export function apply(): void { + if (!isSupported()) { + Object.assign(Promise, {withResolvers}) + } +} diff --git a/test/promise-withResolvers.js b/test/promise-withResolvers.js new file mode 100644 index 0000000..a817cf9 --- /dev/null +++ b/test/promise-withResolvers.js @@ -0,0 +1,40 @@ +import {apply, isPolyfilled, isSupported, withResolvers} from '../lib/promise-withResolvers.js' + +describe('withResolvers', () => { + it('has standard isSupported, isPolyfilled, apply API', () => { + expect(isSupported).to.be.a('function') + expect(isPolyfilled).to.be.a('function') + expect(apply).to.be.a('function') + expect(isSupported()).to.be.a('boolean') + expect(isPolyfilled()).to.equal(false) + }) + + it('resolves to first resolving value', async () => { + const arg = withResolvers() + expect(Object.keys(arg).sort()).to.eql(['promise', 'reject', 'resolve']) + expect(arg).to.have.property('promise').to.be.a('promise') + expect(arg).to.have.property('resolve').to.be.a('function') + expect(arg).to.have.property('reject').to.be.a('function') + + arg.resolve(1) + expect(await arg.promise).to.be.eql(1) + }) + + it('rejects to first rejecting reason', async () => { + const arg = withResolvers() + expect(Object.keys(arg).sort()).to.eql(['promise', 'reject', 'resolve']) + expect(arg).to.have.property('promise').to.be.a('promise') + expect(arg).to.have.property('resolve').to.be.a('function') + expect(arg).to.have.property('reject').to.be.a('function') + + const err = new Error('rejected') + + try { + arg.reject(err) + await arg.promise + expect.fail('should fail') + } catch (e) { + expect(e).to.be.eql(err) + } + }) +})