diff --git a/lib/deferred.js b/lib/deferred.js new file mode 100644 index 0000000..e97c30c --- /dev/null +++ b/lib/deferred.js @@ -0,0 +1,16 @@ +export function deferred(f) { + let promise; + + return { + then( + onfulfilled, + onrejected + ) { + promise ||= new Promise(resolve => resolve(f())); + return promise.then( + onfulfilled, + onrejected + ); + }, + }; +} diff --git a/lib/httpClient.js b/lib/httpClient.js index ae24afb..a8cbbaa 100644 --- a/lib/httpClient.js +++ b/lib/httpClient.js @@ -2,9 +2,10 @@ * Copyright (c) 2020-2022 Digital Bazaar, Inc. All rights reserved. */ import {convertAgent} from './agentCompatibility.js'; +import {deferred} from './deferred.js'; -export const kyOriginalPromise = import('ky-universal') - .then(({default: ky}) => ky); +export const kyOriginalPromise = deferred(() => import('ky-universal') + .then(({default: ky}) => ky)); export const DEFAULT_HEADERS = { Accept: 'application/ld+json, application/json' @@ -33,7 +34,7 @@ export function createInstance({ params = convertAgent(params); // create new ky instance that will asynchronously resolve - const kyPromise = parent.then(kyBase => { + const kyPromise = deferred(() => parent.then(kyBase => { let ky; if(parent === kyOriginalPromise) { // ensure default headers, allow overrides @@ -46,7 +47,7 @@ export function createInstance({ ky = kyBase.extend({headers, ...params}); } return ky; - }); + })); return _createHttpClient(kyPromise); } diff --git a/tests/deferred.spec.js b/tests/deferred.spec.js new file mode 100644 index 0000000..04e6adc --- /dev/null +++ b/tests/deferred.spec.js @@ -0,0 +1,50 @@ +import {deferred} from '../lib/deferred.js'; + +describe('deferred()', () => { + it('resolves to the return value of its function', async () => { + const d = deferred(() => { + return 'return value'; + }); + + const ret = await d; + ret.should.equal('return value'); + }); + + it('defers execution until awaited', async () => { + let executionCount = 0; + executionCount.should.equal(0); + + const d = deferred(() => { + executionCount++; + return 'return value'; + }); + + executionCount.should.equal(0); + await d; + executionCount.should.equal(1); + }); + + it('only executes once', async () => { + let executionCount = 0; + executionCount.should.equal(0); + + const d = deferred(() => { + executionCount++; + return 'return value'; + }); + + await d; + await d; + executionCount.should.equal(1); + }); + + it('unwraps returned promises', async () => { + const d = deferred(() => { + return Promise.resolve('return value'); + }); + + const ret = await d; + ret.should.equal('return value'); + }); +}); +