async-preloader

async-preloader

npm version stability-stable npm minzipped size dependencies types Conventional Commits styled with prettier linted with eslint tested with jest license

Assets preloader using async/await and fetch for usage both in the browser and Node.js.

paypal coinbase twitter

Install

npm install --save async-preloader

Documentation

Quick start

This section covers the basic usage of AsyncPreloader. For more informations about async/await, see Async functions - making promises friendly. Usage in Node.js environment is limited to its capacity to handle fetch requests and DOM APIs. Polyfills like undici/node-fetch (for Node.js below 18) and xmldom might come handy .

Preload items and retrieve them

import AsyncPreloader from "async-preloader";

const items = [
{ id: "myDefaultFile", src: "assets/default" },
{ id: "myTextFile", src: "assets/text.txt" },
{ id: "myJsonFile", src: "assets/json.json" },
{ id: "myImageFile", src: "assets/image.jpg" },
{ id: "myVideoFile", src: "assets/video.mp4" },
{ id: "myAudioFile", src: "assets/audio.mp3" },
{ id: "myXmlFile", src: "assets/xml.xml" },
{ id: "mySvgFile", src: "assets/xml.svg" },
{ id: "myHtmlFile", src: "assets/xml.html" },
{ id: "myDefaultXmlFile", src: "assets/xml", loader: "Xml" },
{ id: "myFont", src: `assets/font.ttf` },
{ id: "Space Regular", loader: "Font", fontOptions: { timeout: 10000 } },
// Can be retrieved with the src property eg. AsyncPreloader.items.get("assets/fileWithoutId")
{ src: "assets/fileWithoutId" },
];

// Pass an array of LoadItem
//
// Returns a Promise with an array of LoadedValue
const pItems = AsyncPreloader.loadItems(items);

pItems
.then((items) => {
const element = AsyncPreloader.items.get("myVideoFile");
document.body.appendChild(element);
})
.catch((error) => console.error("Error loading items", error));

Note: Font loader will try to detect the font in the page using FontFaceObserver when no src is specified.

Load items from a manifest file

It works in a similar fashion as createjs's PreloadJS.

import AsyncPreloader from "async-preloader";

// Pass the file url and an optional path of the property to get in the JSON file.
// It will load the file using the Json loader and look for the path key expecting an array of `LoadItem`s.
// Default path is "items" eg the default manifest would look like this:
// `{ "items": [ { "src": "assets/file1" }, { "src": "assets/file2" }] }`
//
// Returns a Promise with an array of LoadedValue
const pItems = AsyncPreloader.loadManifest(
"assets/manifest.json",
"data.preloader.items"
);

pItems
.then((items) => useLoadedItemsFromManifest(items)) // or AsyncPreloader.items.get("src or id")
.catch((error) => console.error("Error loading items", error));

Advanced usage

This section takes a closer look at the options of AsyncPreloader.

Load a single item by using the loaders directly

import AsyncPreloader from "async-preloader";

// Pass a LoadItem
//
// Returns a Promise with the LoadedValue
const pItem = AsyncPreloader.loadJson({ src: "assets/json.json" });

pItem
.then((item) => useLoadedItem(item))
.catch((error) => console.error("Error loading item", error));

Note: Using the loaders directly won't add the item to the items Map. Alternatively you could use AsyncPreloader.loadItem and rely on the file extension or add { loader: "Json"} to the item.

Load a single item by using a string directly

import AsyncPreloader from "async-preloader";

try {
// Pass a string
//
// Returns a Promise with the LoadedValue
const pItem = await AsyncPreloader.loadItem("assets/json.json");
} catch (error) {
console.error(error);
}

Get an ArrayBuffer instead of the default Blob

You can specify how the response is handle by using the body key in a LoadItem.

Typical use case: get an ArrayBuffer for the WebAudio API to decode the data with baseAudioContext.decodeAudioData().

import AsyncPreloader from "async-preloader";

const audioContext = new AudioContext();
const pItem = AsyncPreloader.loadAudio({
src: "assets/audio.mp3",
body: "arrayBuffer",
});

pItem
.then((item) => audioContext.decodeAudioData(item))
.then((decodedData) => useDecodedData(decodedData))
.catch((error) => console.error("Error decoding audio", error));

Getting the progress

Since fetch doesn't support Progress events yet, you might want to get a per file progress.

import AsyncPreloader from "async-preloader";

const items = [
{ id: "myDefaultFile", src: "assets/default" }, // ...
];

let loadedCount = 0;

async function preload() {
await Promise.all(
items.map(async (item) => {
const data = await AsyncPreloader.loadItem(item);
loadedCount++;
console.log(`Progress: ${(100 * loadedCount) / items.length}%`);
})
);
}

await preload();

Abort one or more loadItem(s) request(s)

To abort a loadItem(s) call, you can create an AbortController instance and pass its signal to options.

const controller = new AbortController();

const timeoutId = setTimeout(() => {
controller.abort();
}, 150);

try {
await AsyncPreloader.loadItems(
items.map((item) => ({
...item,
options: { ...(item.options || {}), signal: controller.signal },
}))
);
} catch (error) {
if (error.name === "AbortError") console.log("Request was aborted");
} finally {
clearTimeout(timeoutId);
}

License

MIT © Damien Seguin