okiba-gang/okiba

View on GitHub
packages/resource-loader/index.js

Summary

Maintainability
A
0 mins
Test Coverage
/**
 * @module ResourceLoader
 * @description Manages loading of resources trough fetch to boost caching.
 * Transparently relies on a WebWorker if possible to load on a separate thread.
 *
 * @example
 * import ResourceLoader from '@okiba/resource-loader'
 *
 * const resLoader = new ResourceLoader()
 *
 * // if `window.Worker` is available
 * // `fetch` happens on a separate thread!
 *
 * urls.forEach(
 *   url => resLoader.load(url)
 *     .then(console.log('Loaded! 📦'))
 *     .catch(console.log('Fail! ☹️'))
 * )
 *
 * @todo  Handle abortion
 * @todo  Fetch is not on IE11
 * @todo  Clone response and pass it as promise reslult
 */
import {createWorker} from '@okiba/worker-utils'

const workerScript = `
  onmessage = ({data}) => {
      self.fetch(data.url, {mode: 'cors'})
        .then(r =>
          postMessage({url: data.url, value: r.ok})
        )
        .catch(_ =>
          postMessage({url: data.url, value: false})
        )
    }
`

class ResourceLoader {
  constructor() {
    this.cache = {}
    if (window.Worker) {
      this.worker = createWorker(workerScript)
    }
  }

  /**
   * Initiates loading of a resource at a given URL
   * @param  {String} url Resource URL
   * @return {Promise} A promise which will be resolved if the resource
   * is loaded and rejected if not.
   */
  load(url) {
    if (this.cache[url]) return this.cache[url]

    this.cache[url] = this.worker
      ? this._loadWithWorker(url)
      : this._loadWithFetch(url)

    this.cache[url]
      .catch(_ => delete this.cache[url])

    return this.cache[url]
  }

  _loadWithWorker(url) {
    const p = new Promise((res, rej) => {
      this.worker.addEventListener(
        'message',
        ({data}) => data.value ? res() : rej()
      )
    })
    this.worker.postMessage({url})
    return p
  }

  _loadWithFetch(url) {
    return new Promise((res, rej) => {
      fetch(url, {mode: 'cors'})
        .then(r => r.ok ? res() : rej())
        .catch(rej)
    })
  }

  destroy() {
    delete this.cache
    if (this.worker) {
      this.worker.terminate()
    }
  }
}

export default ResourceLoader