Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions browser/http/getScript.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -42,4 +42,4 @@ export default (url, callback, timeout = 0) => {
head.insertBefore(script, head.firstChild);

return script;
};
}
56 changes: 56 additions & 0 deletions browser/http/loadScript.js
Original file line number Diff line number Diff line change
@@ -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<HTMLScriptElement>} A promise that resolves with the script element when loaded, or rejects on error.
* @throws {Error<Event>} - Throws an error if the script fails to load or times out.
* @example <caption>Loads a script into the page</caption>
* 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);
});
}
38 changes: 38 additions & 0 deletions test/specs/browser/http/loadScript.spec.js
Original file line number Diff line number Diff line change
@@ -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');
});
});

3 changes: 3 additions & 0 deletions test/specs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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');
Expand All @@ -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');
Expand Down