/async-preloader

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

Primary LanguageJavaScriptMIT LicenseMIT

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