Assets preloader using async/await and fetch for usage both in the browser and Node.js.
npm install --save async-preloader
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 .
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.
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));
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.
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);
}
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));
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();
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);
}
MIT © Damien Seguin