This question already has an answer here:
Weird behavior with Promise throwing "Unhandled promise rejection" error
(1 answer)
Closed 8 months ago.
I'm using Promise.race so I can time out the fetch call. During a test to an API endpoint that doesn't exist, I'm getting an "Unhandled promise rejection" error. The code is supposed to console.log once upon any failure. Where am I not handling the promise rejection? FYI the main function is function Retrieve
window.path = "http://localhost:3000/records";
function isPrimary(color) {
let colorIsPrimary = false;
(color.startsWith("red") || color.startsWith("blue") || color.startsWith("yellow")) ? colorIsPrimary = true : null;
return colorIsPrimary;
}
function transformData(records,page) {
const transformedData = {
"ids" : [],
"open": [],
"closedPrimaryCount": 0,
"previousPage": null,
"nextPage": null
}
let closedPrimaryCount = 0;
records.forEach((record, index) => {
if (index < 10) {
transformedData.ids.push(record.id);
record["isPrimary"] = false;
isPrimary(record.color) ? record.isPrimary = true : null;
record.disposition == "open" ? transformedData.open.push(record) : null;
if (record.disposition == "closed") {
isPrimary(record.color) ? closedPrimaryCount++ : null;
}
}
})
transformedData.closedPrimaryCount = closedPrimaryCount;
let previousPage = null;
page > 1 ? previousPage = page - 1 : null;
transformedData.previousPage = previousPage;
let nextPage = null;
records.length > 10 ? nextPage = page + 1 : null;
transformedData.nextPage = nextPage;
return transformedData;
}
function promiseTimeout(promise, ms) {
let timeout = new Promise((resolve, reject) => {
let timeoutID = setTimeout(() => {
clearTimeout(timeoutID);
reject("fetch failed - timeout");
}, ms)
})
return Promise.race([promise, timeout]);
}
function doFetch(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then((response) => {
if (!response.ok) {
reject(new Error("fetch failed - non 200"));
}
response.json()
.then((records) => {
resolve(records);
})
.catch((error) => {
reject(new Error("fetch failed - error from response.json"));
})
})
.catch((error) => {
reject(new Error("fetch failed - error from fetch"));
})
})
}
function retrieve({page = 1, colors = ["red", "brown", "blue", "yellow", "green"], thetest = false, windowPath = window.path} = {}) {
return new Promise((resolve,reject)=>{
!thetest ? windowPath = "http://localhost:3000/records" : null;
// limit set to 11 so we can determine nextPage
const limit = "11";
const offset = "" + ((page * 10) - 10);
let colorArgs = "";
colors.forEach((color, index) => {
colorArgs = colorArgs + "&color[]=" + color;
});
const requestQuery = `limit=${limit}&offset=${offset}${colorArgs}`;
const requestURL = new URI(windowPath);
requestURL.query(requestQuery);
const promiseRace = promiseTimeout(doFetch(requestURL.toString()), 4000);
promiseRace.then((records) => {
const transformedData = transformData(records, page);
resolve(transformedData);
})
promiseRace.catch((error) => {
console.log(error);
})
});
};
export default retrieve;
After ggorlen's excellent advice, I refactored to this much cleaner (and test-passing) code:
async function getTransformedData(url,page) {
try {
const response = await fetch(url);
if (response.ok) {
const records = await response.json();
const transformedData = transformData(records,page);
return(transformedData);
} else {
throw new Error("failed");
}
}
catch(error) {
console.log(error);
}
// getTransformedData
}
function retrieve({page = 1, colors = ["red", "brown", "blue", "yellow", "green"]} = {}) {
// limit set to 11 so we can determine nextPage
const limit = "11";
const offset = "" + ((page * 10) - 10);
let colorArgs = "";
colors.forEach((color, index) => {
colorArgs = colorArgs + "&color[]=" + color;
});
const requestQuery = `limit=${limit}&offset=${offset}${colorArgs}`;
const requestURL = new URI(window.path);
requestURL.query(requestQuery);
return getTransformedData(requestURL.toString(),page);
};
Good evening! I have a problem.
I am creating a collection of NFTs using this js file.
What I am trying to modify is that once it reads the images in the layers folder, creates the final image and writes the metadata, these read and used images are deleted in the source folder (${basePath}/layers).
const basePath = process.cwd();
const { NETWORK } = require(`${basePath}/constants/network.js`);
const fs = require("fs");
const sha1 = require(`${basePath}/node_modules/sha1`);
const { createCanvas, loadImage } = require(`${basePath}/node_modules/canvas`);
const buildDir = `${basePath}/build`;
const layersDir = `${basePath}/layers`;
const {
format,
baseUri,
description,
background,
uniqueDnaTorrance,
layerConfigurations,
rarityDelimiter,
shuffleLayerConfigurations,
debugLogs,
extraMetadata,
text,
namePrefix,
network,
solanaMetadata,
gif,
} = require(`${basePath}/src/config.js`);
const canvas = createCanvas(format.width, format.height);
const ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = format.smoothing;
var metadataList = [];
var attributesList = [];
var dnaList = new Set();
const DNA_DELIMITER = "-";
const HashlipsGiffer = require(`${basePath}/modules/HashlipsGiffer.js`);
let hashlipsGiffer = null;
const buildSetup = () => {
if (fs.existsSync(buildDir)) {
fs.rmdirSync(buildDir, { recursive: true });
}
fs.mkdirSync(buildDir);
fs.mkdirSync(`${buildDir}/json`);
fs.mkdirSync(`${buildDir}/images`);
if (gif.export) {
fs.mkdirSync(`${buildDir}/gifs`);
}
};
const getRarityWeight = (_str) => {
let nameWithoutExtension = _str.slice(0, -4);
var nameWithoutWeight = Number(
nameWithoutExtension.split(rarityDelimiter).pop()
);
if (isNaN(nameWithoutWeight)) {
nameWithoutWeight = 1;
}
return nameWithoutWeight;
};
const cleanDna = (_str) => {
const withoutOptions = removeQueryStrings(_str);
var dna = Number(withoutOptions.split(":").shift());
return dna;
};
const cleanName = (_str) => {
let nameWithoutExtension = _str.slice(0, -4);
var nameWithoutWeight = nameWithoutExtension.split(rarityDelimiter).shift();
return nameWithoutWeight;
};
const getElements = (path) => {
return fs
.readdirSync(path)
.filter((item) => !/(^|\/)\.[^\/\.]/g.test(item))
.map((i, index) => {
return {
id: index,
name: cleanName(i),
filename: i,
path: `${path}${i}`,
weight: getRarityWeight(i),
};
});
};
const layersSetup = (layersOrder) => {
const layers = layersOrder.map((layerObj, index) => ({
id: index,
elements: getElements(`${layersDir}/${layerObj.name}/`),
name:
layerObj.options?.["displayName"] != undefined
? layerObj.options?.["displayName"]
: layerObj.name,
blend:
layerObj.options?.["blend"] != undefined
? layerObj.options?.["blend"]
: "source-over",
opacity:
layerObj.options?.["opacity"] != undefined
? layerObj.options?.["opacity"]
: 1,
bypassDNA:
layerObj.options?.["bypassDNA"] !== undefined
? layerObj.options?.["bypassDNA"]
: false,
}));
return layers;
};
const saveImage = (_editionCount) => {
fs.writeFileSync(
`${buildDir}/images/${_editionCount}.png`,
canvas.toBuffer("image/png")
);
};
const genColor = () => {
let hue = Math.floor(Math.random() * 360);
let pastel = `hsl(${hue}, 100%, ${background.brightness})`;
return pastel;
};
const drawBackground = () => {
ctx.fillStyle = background.static ? background.default : genColor();
ctx.fillRect(0, 0, format.width, format.height);
};
const addMetadata = (_dna, _edition) => {
let dateTime = Date.now();
let tempMetadata = {
name: `${namePrefix} #${_edition}`,
description: description,
image: `${baseUri}/${_edition}.png`,
dna: sha1(_dna),
edition: _edition,
date: dateTime,
...extraMetadata,
attributes: attributesList,
compiler: "CryptoGioconda Engine",
};
if (network == NETWORK.sol) {
tempMetadata = {
//Added metadata for solana
name: tempMetadata.name,
symbol: solanaMetadata.symbol,
description: tempMetadata.description,
//Added metadata for solana
seller_fee_basis_points: solanaMetadata.seller_fee_basis_points,
image: `image.png`,
//Added metadata for solana
external_url: solanaMetadata.external_url,
edition: _edition,
...extraMetadata,
attributes: tempMetadata.attributes,
properties: {
files: [
{
uri: "image.png",
type: "image/png",
},
],
category: "image",
creators: solanaMetadata.creators,
},
};
}
metadataList.push(tempMetadata);
attributesList = [];
};
const addAttributes = (_element) => {
let selectedElement = _element.layer.selectedElement;
attributesList.push({
trait_type: _element.layer.name,
value: selectedElement.name,
});
};
const loadLayerImg = async (_layer) => {
return new Promise(async (resolve) => {
const image = await loadImage(`${_layer.selectedElement.path}`);
resolve({ layer: _layer, loadedImage: image });
});
};
const addText = (_sig, x, y, size) => {
ctx.fillStyle = text.color;
ctx.font = `${text.weight} ${size}pt ${text.family}`;
ctx.textBaseline = text.baseline;
ctx.textAlign = text.align;
ctx.fillText(_sig, x, y);
};
const drawElement = (_renderObject, _index, _layersLen) => {
ctx.globalAlpha = _renderObject.layer.opacity;
ctx.globalCompositeOperation = _renderObject.layer.blend;
text.only
? addText(
`${_renderObject.layer.name}${text.spacer}${_renderObject.layer.selectedElement.name}`,
text.xGap,
text.yGap * (_index + 1),
text.size
)
: ctx.drawImage(
_renderObject.loadedImage,
0,
0,
format.width,
format.height
);
addAttributes(_renderObject);
};
const constructLayerToDna = (_dna = "", _layers = []) => {
let mappedDnaToLayers = _layers.map((layer, index) => {
let selectedElement = layer.elements.find(
(e) => e.id == cleanDna(_dna.split(DNA_DELIMITER)[index])
);
return {
name: layer.name,
blend: layer.blend,
opacity: layer.opacity,
selectedElement: selectedElement,
};
});
return mappedDnaToLayers;
};
/**
* In some cases a DNA string may contain optional query parameters for options
* such as bypassing the DNA isUnique check, this function filters out those
* items without modifying the stored DNA.
*
* #param {String} _dna New DNA string
* #returns new DNA string with any items that should be filtered, removed.
*/
const filterDNAOptions = (_dna) => {
const dnaItems = _dna.split(DNA_DELIMITER);
const filteredDNA = dnaItems.filter((element) => {
const query = /(\?.*$)/;
const querystring = query.exec(element);
if (!querystring) {
return true;
}
const options = querystring[1].split("&").reduce((r, setting) => {
const keyPairs = setting.split("=");
return { ...r, [keyPairs[0]]: keyPairs[1] };
}, []);
return options.bypassDNA;
});
return filteredDNA.join(DNA_DELIMITER);
};
/**
* Cleaning function for DNA strings. When DNA strings include an option, it
* is added to the filename with a ?setting=value query string. It needs to be
* removed to properly access the file name before Drawing.
*
* #param {String} _dna The entire newDNA string
* #returns Cleaned DNA string without querystring parameters.
*/
const removeQueryStrings = (_dna) => {
const query = /(\?.*$)/;
return _dna.replace(query, "");
};
const isDnaUnique = (_DnaList = new Set(), _dna = "") => {
const _filteredDNA = filterDNAOptions(_dna);
return !_DnaList.has(_filteredDNA);
};
const createDna = (_layers) => {
let randNum = [];
_layers.forEach((layer) => {
var totalWeight = 0;
layer.elements.forEach((element) => {
totalWeight += element.weight;
});
// number between 0 - totalWeight
let random = Math.floor(Math.random() * totalWeight);
for (var i = 0; i < layer.elements.length; i++) {
// subtract the current weight from the random weight until we reach a sub zero value.
random -= layer.elements[i].weight;
if (random < 0) {
return randNum.push(
`${layer.elements[i].id}:${layer.elements[i].filename}${
layer.bypassDNA ? "?bypassDNA=true" : ""
}`
);
}
}
});
return randNum.join(DNA_DELIMITER);
};
const writeMetaData = (_data) => {
fs.writeFileSync(`${buildDir}/json/_metadata.json`, _data);
};
const saveMetaDataSingleFile = (_editionCount) => {
let metadata = metadataList.find((meta) => meta.edition == _editionCount);
debugLogs
? console.log(
`Writing metadata for ${_editionCount}: ${JSON.stringify(metadata)}`
)
: null;
fs.writeFileSync(
`${buildDir}/json/${_editionCount}.json`,
JSON.stringify(metadata, null, 2)
);
};
function shuffle(array) {
let currentIndex = array.length,
randomIndex;
while (currentIndex != 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[array[currentIndex], array[randomIndex]] = [
array[randomIndex],
array[currentIndex],
];
}
return array;
}
const startCreating = async () => {
let layerConfigIndex = 0;
let editionCount = 1;
let failedCount = 0;
let abstractedIndexes = [];
for (
let i = network == NETWORK.sol ? 0 : 1;
i <= layerConfigurations[layerConfigurations.length - 1].growEditionSizeTo;
i++
) {
abstractedIndexes.push(i);
}
if (shuffleLayerConfigurations) {
abstractedIndexes = shuffle(abstractedIndexes);
}
debugLogs
? console.log("Editions left to create: ", abstractedIndexes)
: null;
while (layerConfigIndex < layerConfigurations.length) {
const layers = layersSetup(
layerConfigurations[layerConfigIndex].layersOrder
);
while (
editionCount <= layerConfigurations[layerConfigIndex].growEditionSizeTo
) {
let newDna = createDna(layers);
if (isDnaUnique(dnaList, newDna)) {
let results = constructLayerToDna(newDna, layers);
let loadedElements = [];
results.forEach((layer) => {
loadedElements.push(loadLayerImg(layer));
});
await Promise.all(loadedElements).then((renderObjectArray) => {
debugLogs ? console.log("Clearing canvas") : null;
ctx.clearRect(0, 0, format.width, format.height);
if (gif.export) {
hashlipsGiffer = new HashlipsGiffer(
canvas,
ctx,
`${buildDir}/gifs/${abstractedIndexes[0]}.gif`,
gif.repeat,
gif.quality,
gif.delay
);
hashlipsGiffer.start();
}
if (background.generate) {
drawBackground();
}
renderObjectArray.forEach((renderObject, index) => {
drawElement(
renderObject,
index,
layerConfigurations[layerConfigIndex].layersOrder.length
);
if (gif.export) {
hashlipsGiffer.add();
}
});
if (gif.export) {
hashlipsGiffer.stop();
}
debugLogs
? console.log("Editions left to create: ", abstractedIndexes)
: null;
saveImage(abstractedIndexes[0]);
addMetadata(newDna, abstractedIndexes[0]);
saveMetaDataSingleFile(abstractedIndexes[0]);
console.log(
`Created edition: ${abstractedIndexes[0]}, with DNA: ${sha1(
newDna
)}`
);
});
dnaList.add(filterDNAOptions(newDna));
editionCount++;
abstractedIndexes.shift();
} else {
console.log("DNA exists!");
failedCount++;
if (failedCount >= uniqueDnaTorrance) {
console.log(
`You need more layers or elements to grow your edition to ${layerConfigurations[layerConfigIndex].growEditionSizeTo} artworks!`
);
process.exit();
}
}
}
layerConfigIndex++;
}
writeMetaData(JSON.stringify(metadataList, null, 2));
};
module.exports = { startCreating, buildSetup, getElements };
I tried to use this code by inserting it inside the main document, but it doesn't work.
try {
fs.unlinkSync(path)
//file removed
} catch(err) {
console.error(err)
}
Can anyone tell me how can I solve?
EDIT
i was able to get it to work, but one problem now is, before it creates and additional empty item before showing other items.
NB the load on demand function works fine, but i don't know why an additional empty item is created. i guess there's an issue with my code
const viewModel = observableModule.fromObject({
_sourceDataItems: [],
dataItems: new ObservableArray(),
initDataItems: function () {
var url="https://adekunletestprojects.000webhostapp.com/skog/searchResults.php?search=" + encodeURIComponent("Adeyeye") + "&location=" + encodeURIComponent("Lagos");
fetch(url).then((response) => response.json()).then((res) => {
this._sourceDataItems = new ObservableArray(res.items);
this.dataItems.push(this._sourceDataItems);
}).catch((err) => {
var toast = Toast.makeText("Unable to load users");
toast.show();
});
},
addMoreItemsFromSource: function (chunkSize) {
console.log(this._sourceDataItems);
let newItems = this._sourceDataItems.splice(0, chunkSize);
this.dataItems.push(newItems);
},
onLoadMoreItemsRequested: function (args) {
console.log("---load more item---");
const that = new WeakRef(this);
const listView = args.object;
if (this._sourceDataItems.length > 0) {
setTimeout(function () {
that.get().addMoreItemsFromSource(10);
listView.notifyLoadOnDemandFinished();
}, 1500);
args.returnValue = true;
} else {
args.returnValue = false;
listView.notifyLoadOnDemandFinished(true);
}
},
});
Search-view-model.js
_sourceDataItems: new ObservableArray(),
dataItems: new ObservableArray(),
initDataItems: function () {
var url = "https://adekunletestprojects.000webhostapp.com/skog/searchResults.php?search=" + encodeURIComponent("Adeyeye") + "&location=" + encodeURIComponent("Lagos");
fetch(url).then((response) => response.json()).then((res) => {
this._sourceDataItems = res.items;
this.addMoreItemsFromSource(6);
}).catch((err) => {
alert(err.message);
});
},
addMoreItemsFromSource: function (chunkSize) {
console.log(this._sourceDataItems);
let newItems = this._sourceDataItems.splice(0, chunkSize);
this.dataItems.push(newItems);
},
onLoadMoreItemsRequested: function (args) {
console.log("---load more item---");
const that = new WeakRef(this);
const listView = args.object;
if (this._sourceDataItems.length > 0) {
setTimeout(function () {
that.get().addMoreItemsFromSource(10);
listView.notifyLoadOnDemandFinished();
}, 1500);
args.returnValue = true;
} else {
args.returnValue = false;
listView.notifyLoadOnDemandFinished(true);
}
},
Search.js
exports.pageLoaded = function (args) {
const page = args.object;
var searchViewModel = new SearchViewModel();
page.bindingContext = searchViewModel;
searchViewModel.initDataItems();
searchViewModel.addMoreItemsFromSource(5);
}
I have two .js-files:
main.js
require("./randomEvent.js").start("hey");
require("./randomEvent.js").start("hi");
require("./randomEvent.js").start("hello");
randomEvent.js
var repeat = true;
exports.start = (randomString) => {
while (repeat) {
console.log(randomString);
}
}
exports.stop = (randomString) => {
repeat = false;
}
I want to start randomEvent.js 3 times, each with different randomStrings.
And if I do
require("./randomEvent.js").stop("hi");
it should stop the start("hi") function / it should set repeat to false.
How?
Thanks in advance!
You can implement your randomEvents.js as a class. So every instance has its own repeat flag.
function RandomEvent(str) {
this.repeat = true;
this.randomString = str;
this.start = () => {
setInterval(() => {
if (this.repeat) {
console.log('-->', this.randomString);
}
}, 1000);
}
this.stop = () => {
this.repeat = false;
}
}
module.exports = RandomEvent;
and main.js
let RandomEvent = require('./randomEvent');
let sayHi = new RandomEvent('hi');
let sayHello = new RandomEvent('hello');
let sayHey = new RandomEvent('hey');
sayHi.start();
sayHello.start();
sayHey.start();
setTimeout(() => {
console.log('stop saying hi')
sayHi.stop();
}, 5000);
Or you can store for every string, its own flag:
randomEvents.js
var repeat = {};
exports.start = (randomString) => {
repeat[randomString] = true;
setInterval(() => {
if (repeat[randomString]) {
console.log('-->', randomString);
}
}, 1000);
}
exports.stop = (randomString) => {
repeat[randomString] = false;
}
and in main.js
require("./randomEvent.js").start("hey");
require("./randomEvent.js").start("hi");
require("./randomEvent.js").start("hello");
setTimeout(() => {
console.log('stop saying hi')
require("./randomEvent.js").stop("hi");
}, 5000);
randomEvent.js
var repeatFlags = {};
function repeatLog(str) {
if (repeatFlags[str]) {
console.log(str);
setTimeout(() => {
repeatLog(str);
});
}
}
exports.start = (randomString) => {
repeatFlags[randomString] = true;
repeatLog(randomString);
}
exports.stop = (randomString) => {
repeatFlags[randomString] = false;
}
I'm looking to repeat and stack on methods to a chain depending on length x;
const thing = ObjWithMethods
thing
.something()
.somethingElse()
.repeat(() => {
for (let i = 0; i < x; i++) {
thing.repeatedMethod()
}
})
.endMethods();
Is there a way to do this?
Edit:
This is the current concrete example I have
import HummusRecipe from 'hummus-recipe';
const getPageCount = require('docx-pdf-pagecount');
const generateFooterText = (index, length) => `Page ${index} / ${length}`;
const applyPdfFooter = (input, output, cb) => {
const throwErr = (err) => {cb(err);};
const recipe = new HummusRecipe(input, output, pdfOptions);
getPageCount(input)
.then((pages) => {
console.log('Pages: ', pages);
recipe
.and(function(recipe) {
for (let i = 0; i < pages; i++) {
const text = generateFooterText(i, pages);
recipe
.editPage(i)
.text(text, 0, 0)
.endPage();
}
})
.endPDF();
cb();
})
.catch(throwErr);
};
export default applyPdfFooter;
your recipe could be implemented as:
class Recipe {
constructor(input, output, options) {
this.page = 0;
}
and(fn) {
fn(this);
return this; // chaining
}
editPage(i) {
this.page = i;
return this;
}
text(...args) {
//...
return this;
}
endPage() {
this.page++;
return this;
}
endPdf() { }
}
... but I'm not sure if that and(...) method has any benefit...,