Using Javascript Promise.allSettled() to apply multiple XSLT transformations

Javascript Promises provide methods to help with asynchronous loading of resources, which can be helpful for loading multiple XSLT stylesheets.

XSLT for the Modern Web
6 min read6 days ago

Introduction

For this post I’ll use this Javascript extract as a starting point, which you will find in previous posts in this series on using XSLT for the Modern Web.

(function(){

function fetchLoad(){

if(!('fetch' in window)) {
console.log('Fetch does not appear to be available in this browser. Please try another.');
return;
}

if(!('XSLTProcessor' in window)) {
console.log('XSLTProcessor does not appear to be available in this browser. Please try another.');
return;
}

if(!('DOMParser' in window)){
console.log('DOMParser does not appear to be available in this browser. Please try another.');
return;
}

const xsltProcessor = new XSLTProcessor();
const parser = new DOMParser();
loadFile("data/herodotus.xsl").then(data => {
const xsl = parser.parseFromString(data, "application/xml");
xsltProcessor.importStylesheet(xsl);
}).then(loadFile("data/Perseus_text_1999.01.0126.xml").then(data => {
const xml = parser.parseFromString(data, "application/xml");
const fragment = xsltProcessor.transformToFragment(xml, document);
document.body.appendChild(fragment);
}));
}

async function loadFile(filepath){
const response = await fetch(filepath);
if(!response.ok){
console.log('Looks like there was a problem: ', response.status);
}
const text = await response.text();
return text;
}

window.addEventListener("load", fetchLoad, false);
})();

Javascript Promises

Promises are a relatively new feature of Javascript to allow for asynchronous use and to avoid the so-called “callback hell” that occurs when multiple closure callbacks are used in Javascript but also allow chaining and sequencing of operations. This can be useful in situations where it is known that resources need to be loaded and this may take time or when there is a need for one section of Javascript to execute after another.

The Javascript provided above is already using Promises discreetly — the fetch API returns a Promise via the loadFile() function, and you can see the .then() method when the function is called.

If you see a .then() method being called on an object in Javascript — you are looking at a Promise!

loadFile("data/herodotus.xsl").then(data => {
const xsl = parser.parseFromString(data, "application/xml");
xsltProcessor.importStylesheet(xsl);
}).then(loadFile("data/Perseus_text_1999.01.0126.xml").then(data => {
const xml = parser.parseFromString(data, "application/xml");
const fragment = xsltProcessor.transformToFragment(xml, document);
document.body.appendChild(fragment);
}));

Promise methods

There are various Promise methods we can use to build upon this example.

  • Promise.all() — fulfills when all of the promises fulfill; rejects when any of the promises reject. This is to be used when it is important that all the Promises fulfill, since if any of the Promises reject, the script will not be executed.
  • Promise.allSettled() — fulfills when all promises ‘settle’ — meaning that the Promise has reached a state that is either fulfilled or rejected. This is to be used when all of the Promises are known to have either fulfilled or rejected.
  • Promise.any() — fulfills when any of the promises fulfills; rejects when all of the promises reject. This is to be used when you want to know at least one of the Promises has fulfilled, without necessarily caring which.
  • Promise.race() — Fulfills when any of the promises settles. Fulfills when any of the promises fulfills; rejects when any of the promises reject. This is to be used when you want to know which Promise has fulfilled quickest.

In this example I want to show an example of loading multiple XSLT stylesheet files — for this I could use .all() but for this example I will instead use the .allSettled() method — this is because if an error is found on loading one of the stylesheets, I still want the other stylesheets to be transformed.

A bit of Javascript fun could be had by doing all four as an exercise!

(function(){

function fetchLoad(){

if(!('fetch' in window)) {
console.log('Fetch does not appear to be available in this browser. Please try another.');
return;
}

if(!('XSLTProcessor' in window)) {
console.log('XSLTProcessor does not appear to be available in this browser. Please try another.');
return;
}

if(!('DOMParser' in window)){
console.log('DOMParser does not appear to be available in this browser. Please try another.');
return;
}

const xsltProcessor = new XSLTProcessor();
const parser = new DOMParser();

const xml = loadFile("data/Perseus_text_1999.01.0126.xml");

const xsltnames = loadFile("data/herodotus-names.xsl");
const xsltplaces = loadFile("data/herodotus-places.xsl");
const xsltethnic = loadFile("data/herodotus-ethnic.xsl");

const promiseList = [xml, xsltnames, xsltplaces, xsltethnic];

Promise.allSettled(promiseList).then((data) => {

const xmlStr = parser.parseFromString(data[0].value, "application/xml");
let xslt, transform;

xslt = parser.parseFromString(data[1].value, "application/xml");
xsltProcessor.importStylesheet(xslt);
transform = xsltProcessor.transformToFragment(xmlStr, document);
document.body.appendChild(transform);

xsltProcessor.reset();

xslt = parser.parseFromString(data[2].value, "application/xml");
xsltProcessor.importStylesheet(xslt);
transform = xsltProcessor.transformToFragment(xmlStr, document);
document.body.appendChild(transform);

xsltProcessor.reset();

xslt = parser.parseFromString(data[3].value, "application/xml");
xsltProcessor.importStylesheet(xslt);
transform = xsltProcessor.transformToFragment(xmlStr, document);
document.body.appendChild(transform);

}).catch((error) => {
console.error("one of the promises rejected", error);
});
}

async function loadFile(filepath){

const response = await fetch(filepath);
if(!response.ok){
console.log('Looks like there was a problem: ', response.status);
}
const text = await response.text();
return text;
}

window.addEventListener("load", fetchLoad, false);
})();

The Promise.allSettled() method requires array of Promises to be passed.

We covered above that in this case the loadFile() function returns a Promise, so simply passing in an array with the handle variables from the loadFile() function will do the trick.

This point may be non-intuitive — the loadFile() function is already asynchronous as it uses the Await / Async syntax so each of the xsltnames, xsltplaces, xsltethnic is in fact a handle to a Javascript Promise.

The variable xml is in this case a handle for an XML edition of the Perseus Herodotus The Histories text as mentioned in a previous post.

  const xml = loadFile("data/Perseus_text_1999.01.0126.xml");

const xsltnames = loadFile("data/herodotus-names.xsl");
const xsltplaces = loadFile("data/herodotus-places.xsl");
const xsltethnic = loadFile("data/herodotus-ethnic.xsl");

const promiseList = [xml, xsltnames, xsltplaces, xsltethnic];

We make an anonymous Promise simply with the keyword Promise, the .then() method of the Promise only gets executed when all the Promises in the array are settled. (If we needed to refer to this Promise at a later point in the Javascript we might have been better off giving it a variable handle, but in this small example it doesn’t matter).

Promise.allSettled(promiseList).then((data) => {

//only executed when the promises in promiseList are all settled

}).catch((error) => {
console.error("one of the promises rejected", error);
});

The variable data in this case contains a full array of Promise objects. The variable data is not available outside of the .then() method and as this represents an array of Promise objects, each object in the array will have the properties “value” and “status”. (By this point in the Javascript, all the Promises will have the status property as “fulfilled” — unless there is an error).

Promise.allSettled(promiseList).then((data) => {

//data[N].value
//data[N].status

}).catch((error) => {
console.error("one of the promises rejected", error);
});

Having known that the XML file and XSLT stylesheets are loaded, we can now parse, import the XSLT stylesheets and apply the transformations. Note that to provide multiple transformations with the Javascript XSLTProcessor object, it is necessary that the .reset() method is called between each XSLT transformation.

(You’ll have to note here that this is a simple example and what we haven’t considered here is whether the parsing of the string, the .importStylesheet() method or the .transformToFragement() method also need to be ‘Promis-ified ‘— one to look into further).

Promise.allSettled(promiseList).then((data) => {

const xmlStr = parser.parseFromString(data[0].value, "application/xml");
let xslt, transform;

xslt = parser.parseFromString(data[1].value, "application/xml");
xsltProcessor.importStylesheet(xslt);
transform = xsltProcessor.transformToFragment(xmlStr, document);
document.body.appendChild(transform);

xsltProcessor.reset();

xslt = parser.parseFromString(data[2].value, "application/xml");
xsltProcessor.importStylesheet(xslt);
transform = xsltProcessor.transformToFragment(xmlStr, document);
document.body.appendChild(transform);

xsltProcessor.reset();

xslt = parser.parseFromString(data[3].value, "application/xml");
xsltProcessor.importStylesheet(xslt);
transform = xsltProcessor.transformToFragment(xmlStr, document);
document.body.appendChild(transform);

}).catch((error) => {
console.error("one of the promises rejected", error);
});

Finally, the closing part of the Promise is the .catch() method. Here we have an console.error() in the case that a promise is rejected.

}).catch((error) => {
console.error("one of the promises rejected", error);
});

This has been a simple example of loading multiple XSLT stylesheets and using the Promise.allSettled() method.

If you’ve found this interesting and want to explore similar articles, check out these other articles:

If you want to learn more about XSLT, how to use it and understand its place in the Modern Web — follow to learn more.

Beginners XSLT patterns so far:

--

--

XSLT for the Modern Web

Re-learning XSLT in the context of the Modern Web - occassional writer, reader, Digital Humanities enthusiast All text is 100% non-AI - learn, explore, reuse