Skip to content

fetch includes asynchronously (in-browser / client-side) #630

@tigregalis

Description

@tigregalis

I'd like to fetch documents and includes asynchronously (in-browser), using fetch.

By default, it uses synchronous XMLHttpRequest, which locks the browser and also give us a warning:

Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help http://xhr.spec.whatwg.org/    asciidoctor.js:23436:8 

That's why I'd like to suggest a loadAsync / convertAsync API for Asciidoctor.js.

Perhaps a related suggestion is a way for the user to specify their own http client, e.g. if someone wishes to use fetch, axios, or node-fetch.

As a really convoluted work-around / proof-of-concept, I've managed to use two include processor extensions and Promises to progressively "load" the document. Right now this will only go down to one level of includes. There are a number of other issues of course, but primarily is that it's loading/parsing the document more than once.

const asciidoctor = Asciidoctor();

const root = document.getElementById('root');

function promiseObject(object) {

  let promisedProperties = [];

  const objectKeys = Object.keys(object);

  objectKeys.forEach((key) => promisedProperties.push(object[key]));

  return Promise.all(promisedProperties)
    .then((resolvedValues) => {
      return resolvedValues.reduce((resolvedObject, property, index) => {
        resolvedObject[objectKeys[index]] = property;
        return resolvedObject;
      }, object);
    });

}

fetch('index.adoc').then(res => res.text()).then(content => {

  const includesMap = {};

  const registry1 = asciidoctor.Extensions.create();
  registry1.includeProcessor(function () {
    var self = this;
    self.handles(_ => true);
    self.process(function (doc, reader, target, attrs) {
      if (!includesMap[target]) includesMap[target] = fetch(target).then(res => res.text());
      return reader.pushInclude('pending...', target, target, 1, attrs);
    });
  });

  asciidoctor.load(content, {'safe': 'safe', extension_registry: registry1});

  promiseObject(includesMap).then(includes => {

    const registry2 = asciidoctor.Extensions.create();
    registry2.includeProcessor(function () {
      var self = this;
      self.handles(_ => true);
      self.process(function (doc, reader, target, attrs) {
        return reader.pushInclude(includes[target], target, target, 1, attrs);
      });
    });

    const doc = asciidoctor.load(content, {'safe': 'safe', extension_registry: registry2});
    const html = doc.convert();
    root.innerHTML = html;

  });

});

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions