Skip to content

Commit

Permalink
feat: use Promise.allSettled in load
Browse files Browse the repository at this point in the history
- always return results even when erroring so user can still get loaded assets
- add map from key to loadFn
- update example
- add error to example
  • Loading branch information
dmnsgn committed Mar 16, 2022
1 parent 03bd818 commit 973fe5a
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 102 deletions.
118 changes: 87 additions & 31 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,17 @@ <h1>pex-io</h1>
json: "assets/color.json",
image: "assets/pex.png",
binary: "assets/data.bin",
errorNotFound: "assets/not-found.ext",
};

const assetState = { text: null, json: null, image: null, binary: null };

const state = {
callback: { ...assetState },
callbackBatch: { ...assetState },
"callback-batch": { ...assetState },
async: { ...assetState },
asyncBatch: { ...assetState },
"async-batch": { ...assetState },
error: { ...assetState, error: null },
};

const app = () => () => {
Expand All @@ -65,6 +67,7 @@ <h1>pex-io</h1>
{ style: { display: "flex", gap: "60px" } },
Object.keys(state).map((technique) => [
"div",
{ style: { "flex-basis": "100%" } },
["h2", technique],
Object.keys(state[technique]).map((itemName) => {
let value = state[technique][itemName];
Expand All @@ -81,26 +84,28 @@ <h1>pex-io</h1>

hdom.start(app(), { root: document.querySelector(".Result") });

const filterResultsErrors = (results) =>
Object.fromEntries(
Object.entries(results).filter(([_, v]) => v instanceof Error)
);

// callback
(() => {
io.loadText(ASSETS.text, (err, text) => {
if (err) return console.log(err);
state.callback.text = text;
state.callback.text = err || text;
if (err) console.log("callback", err);
});

io.loadJSON(ASSETS.json, (err, json) => {
if (err) return console.log(err);
state.callback.json = json;
state.callback.json = err || json;
if (err) console.log("callback", err);
});

io.loadImage(ASSETS.image, (err, image) => {
if (err) return console.log(err);
state.callback.image = image;
state.callback.image = err || image;
if (err) console.log("callback", err);
});

io.loadBinary(ASSETS.binary, (err, data) => {
if (err) return console.log(err);
state.callback.binary = data;
state.callback.binary = err || data;
if (err) console.log("callback", err);
});
})();

Expand All @@ -114,35 +119,86 @@ <h1>pex-io</h1>
data: { binary: ASSETS.binary },
},
(err, res) => {
if (err) return console.log(err);
state.callbackBatch.text = res.hello;
state.callbackBatch.json = res.color;
state.callbackBatch.image = res.pex;
state.callbackBatch.binary = res.data;
state["callback-batch"].text = res.hello;
state["callback-batch"].json = res.color;
state["callback-batch"].image = res.pex;
state["callback-batch"].binary = res.data;
if (err)
return console.log("callback batch", filterResultsErrors(err));
}
);
})();

// async
(async () => {
state.async.text = await io.loadText(ASSETS.text);
state.async.json = await io.loadJSON(ASSETS.json);
state.async.image = await io.loadImage(ASSETS.image);
state.async.binary = await io.loadBinary(ASSETS.json);
try {
state.async.text = await io.loadText(ASSETS.text);
} catch (error) {
state.async.text = error;
console.log("async/await", error);
}
try {
state.async.json = await io.loadJSON(ASSETS.json);
} catch (error) {
state.async.json = error;
console.log("async/await", error);
}
try {
state.async.image = await io.loadImage(ASSETS.image);
} catch (error) {
state.async.image = error;
console.log("async/await", error);
}
try {
state.async.binary = await io.loadBinary(ASSETS.binary);
} catch (error) {
state.async.binary = error;
console.log("async/await", error);
}
})();

// async batch
(async () => {
const res = await io.load({
hello: { text: ASSETS.text },
color: { json: ASSETS.json },
pex: { image: ASSETS.image },
data: { binary: ASSETS.binary },
let res;
try {
res = await io.load({
hello: { text: ASSETS.text },
color: { json: ASSETS.json },
pex: { image: ASSETS.image },
data: { binary: ASSETS.binary },
});
} catch (err) {
res = err;
console.log("async/await batch", filterResultsErrors(err));
}
state["async-batch"].text = res.hello;
state["async-batch"].json = res.color;
state["async-batch"].image = res.pex;
state["async-batch"].binary = res.data;
})();

// Error
(() => {
io.loadText(ASSETS.errorNotFound, (err, text) => {
state.error.text = err || text;
if (err) console.log("error", err);
});
io.loadJSON(ASSETS.errorNotFound, (err, json) => {
state.error.json = err || json;
if (err) console.log("error", err);
});
io.loadImage(ASSETS.errorNotFound, (err, image) => {
state.error.image = err || image;
if (err) console.log("error", err);
});
io.loadBinary(ASSETS.errorNotFound, (err, data) => {
state.error.binary = err || data;
if (err) console.log("error", err);
});
io.load({ errorUnknownType: { foo: "bar" } }, (err, res) => {
state.error.error = res.errorUnknownType;
if (err) return console.log("error", filterResultsErrors(err));
});
state.asyncBatch.text = res.hello;
state.asyncBatch.json = res.color;
state.asyncBatch.image = res.pex;
state.asyncBatch.binary = res.data;
})();
</script>
</body>
Expand Down
134 changes: 64 additions & 70 deletions load.js
Original file line number Diff line number Diff line change
@@ -1,86 +1,80 @@
import loadImage from "./loadImage.js";
import loadBinary from "./loadBinary.js";
import loadText from "./loadText.js";
import loadJSON from "./loadJSON.js";
import loadImage from "./loadImage.js";
import loadBinary from "./loadBinary.js";
import { promisify } from "./utils.js";

/**
* Loads provided resources
* @param {Object} resources - map of resources, see example
* @param {Function} callback function(err, resources), see example
* @returns {Object} - with same properties are resource list but resolved to the actual data
*
* @example
* var resources = {
* img : { image: __dirname + '/tex.jpg'},
* hdrImg : { binary: __dirname + '/tex.hdr'}
* data : { json: __dirname + '/data.json'},
* hello : { text: __dirname + '/hello.txt'}
* }
* load(resources, function(err, res) {
* res.img //{Image} in a Browser or {SkCanvas} in Plask
* res.hdrImg //{ArrayBuffer}
* res.data //{JSON}
* res.hello //{string}
* })
* @private
*/
function load(resources, callback) {
const results = {};
const errors = {};
let hadErrors = false;
const LOADERS_MAP = {
text: loadText,
json: loadJSON,
image: loadImage,
binary: loadBinary,
};
const LOADERS_MAP_KEYS = Object.keys(LOADERS_MAP);

// TODO: use `async` module instead?
let loadedResources = 0;
const resourceNames = Object.keys(resources);
const numResources = resourceNames.length;
/**
* @typedef {Object} Resource
* @property {string} [text]
* @property {string} [json]
* @property {string} [image]
* @property {string} [binary]
*/

function onFinish() {
try {
if (hadErrors) {
callback(errors, null);
} else {
callback(null, results);
}
} catch (e) {
console.log(e);
console.log(e.stack);
}
}
/**
* @callback resourceCallback
* @param {Error} err
* @param {Object.<string, string | Object | HTMLImageElement | ArrayBuffer>} res
*/

resourceNames.forEach((name) => {
function onLoaded(err, data) {
if (err) {
hadErrors = true;
errors[name] = err;
} else {
results[name] = data;
}
/**
* Loads resources from a named map
* @param {Object.<string, Resource>} resources
* @param {resourceCallback} callback
*
* @example
* const resources = {
* hello: { text: "assets/hello.txt" },
* data: { json: "assets/data.json" },
* img: { image: "assets/tex.jpg" },
* hdrImg: { binary: "assets/tex.hdr" },
* };
*
* io.load(resources, (err, res) => {
* res.hello; // => String
* res.data; // => Object
* res.img; // => HTMLImageElement
* res.hdrImg; // => ArrayBuffer
* if (err) return console.log(err);
* });
*/
function load(resources, callback) {
const names = Object.keys(resources);

if (++loadedResources === numResources) {
onFinish();
}
}
Promise.allSettled(
names.map(async (name) => {
const res = resources[name];

const res = resources[name];
if (res.image) {
loadImage(res.image, onLoaded);
} else if (res.text) {
loadText(res.text, onLoaded);
} else if (res.json) {
loadJSON(res.json, onLoaded);
} else if (res.binary) {
loadBinary(res.binary, onLoaded);
} else {
onLoaded(
new Error(`pex-io/load unknown resource type ${Object.keys(res)}`),
null
const loader = LOADERS_MAP_KEYS.find((loader) => res[loader]);
if (loader) return await LOADERS_MAP[loader](res[loader]);
return Promise.reject(
new Error(`io.load: unknown resource type ${Object.keys(res)}`)
);
}
})
).then((values) => {
const results = Object.fromEntries(
Array.from(
values.map((v) => v.value || v.reason),
(v, i) => [names[i], v]
)
);
callback(
values.find((v) => v.status === "rejected") ? results : null,
results
);
});

if (resourceNames.length === 0) {
onFinish();
}
}

export default promisify(load);
2 changes: 1 addition & 1 deletion loadImage.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function loadImage(opts, callback) {
img.crossOrigin = crossOrigin;
}
img.onerror = () => {
callback(new Error(`io.loadImage: Failed to load ${url}`), null);
callback(new Error(`io.loadImage: load error ${url}`), null);
};
img.onload = () => {
callback(null, img);
Expand Down

0 comments on commit 973fe5a

Please sign in to comment.