diff --git a/browser/http/getScript.js b/browser/http/getScript.js index 204799d..fdaebc1 100644 --- a/browser/http/getScript.js +++ b/browser/http/getScript.js @@ -1,7 +1,7 @@ import createElement from '../../dom/createElement.js'; import createEvent from '../../events/createEvent.js'; -export default (url, callback, timeout = 0) => { +export default function getScript(url, callback, timeout = 0) { // Inject a script tag let bool = 0; @@ -42,4 +42,4 @@ export default (url, callback, timeout = 0) => { head.insertBefore(script, head.firstChild); return script; -}; +} diff --git a/browser/http/loadScript.js b/browser/http/loadScript.js new file mode 100644 index 0000000..50e2053 --- /dev/null +++ b/browser/http/loadScript.js @@ -0,0 +1,56 @@ +import createElement from '../../dom/createElement.js'; +import createEvent from '../../events/createEvent.js'; + +/** + * Load a script from a URL and return a promise that resolves when the script has loaded + * @param {string} url - An absolute or relative URL to the script to load. + * @param {number} [timeout=0] - Optional timeout in milliseconds. If 0 no timeout is applied. + * @returns {Promise} A promise that resolves with the script element when loaded, or rejects on error. + * @throws {Error} - Throws an error if the script fails to load or times out. + * @example Loads a script into the page + * await loadScript('https://example.com/script.js') // Load a remote script with a 5 second timeout + * await loadScript('./local.js', 5000) // Load a local script with a 5 second timeout + */ +export default function loadScript(url, timeout = 0) { + + return new Promise((resolve, reject) => { + // Inject a script tag + const head = document.getElementsByTagName('script')[0].parentNode; + + let timer; + + // Resolve + + // Add timeout + if (timeout) { + timer = window.setTimeout(() => { + reject(createEvent('timeout')); + }, timeout); + } + + // Build script tag + const script = createElement('script', { + src: url, + onerror(e) { + window.clearTimeout(timer); + reject(e); + }, + onload() { + window.clearTimeout(timer); + resolve(script); + }, + onreadystatechange: () => { + if (/loaded|complete/i.test(script.readyState)) { + window.clearTimeout(timer); + resolve(script); + } + } + }); + + // Set Async + script.async = true; + + // Inject script tag into the head element + head.insertBefore(script, head.firstChild); + }); +} diff --git a/test/specs/browser/http/loadScript.spec.js b/test/specs/browser/http/loadScript.spec.js new file mode 100644 index 0000000..e117d6b --- /dev/null +++ b/test/specs/browser/http/loadScript.spec.js @@ -0,0 +1,38 @@ +import loadScript from '../../../../browser/http/loadScript.js'; + +describe('http/loadScript', () => { + + const jsonpMockUrl = './mock-jsonp.js'; + + it('should trigger a callback once the script has loaded', async () => { + + const url = `${jsonpMockUrl}?callback=test_getScript`; + + // This mock endpoint is used + const spy = sinon.spy(); + + window.test_getScript = spy; + + const scriptTag = await loadScript(url); + + expect(scriptTag.tagName).to.equal('SCRIPT'); + }); + + it('should return a response even if the endpoint returned an error', async () => { + const url = '404.js'; + try { + await loadScript(url); + } + catch (e) { + // IE9 triggers a loaded event + // Its best to determine whether its loaded by the presence of the API it's exposing. + if (e.type !== 'load') { + // However in all other browsers this should trigger the onerror handler + expect(e).to.have.property('type', 'error'); + } + return; + } + throw new Error('This should not resolve'); + }); +}); + diff --git a/test/specs/index.js b/test/specs/index.js index 0f846cc..fbacea5 100644 --- a/test/specs/index.js +++ b/test/specs/index.js @@ -8,6 +8,7 @@ import('./browser/agent/indexStorage.spec.js'); import('./browser/http/formpost.spec.js'); import('./browser/http/getScript.spec.js'); import('./browser/http/jsosnp.spec.js'); +import('./browser/http/loadScript.spec.js'); import('./browser/http/xhr.spec.js'); import('./dom/append.spec.js'); import('./dom/attr.spec.js'); @@ -29,6 +30,7 @@ import('./events/globalCallback.spec.js'); import('./events/off.spec.js'); import('./events/on.spec.js'); import('./events/visible.spec.js'); +import('./map/compare.spec.js'); import('./object/Defer.spec.js'); import('./object/Queue.spec.js'); import('./object/args.spec.js'); @@ -55,6 +57,7 @@ import('./object/until.spec.js'); import('./string/capitalize.spec.js'); import('./string/createUrl.spec.js'); import('./string/extract.spec.js'); +import('./string/isJSON.spec.js'); import('./string/jsonParse.spec.js'); import('./string/ltrim.spec.js'); import('./string/param.spec.js');