Understanding factory error handler step-by-step - javascript

i'm reading and learning about promises/async/await.
I'm a little stuck trying to understand step by step an error handling method that i already seen a couple of times, its a factory function approach for dealing with errors when creating promises.
Code with comments (questions below)
const myPromise = new Promise((resolve, reject)=>
setTimeout(()=> console.log('ERROR MESSAGE'),1000))
// async / await with error handler factory
const testPromise = async () => {
var data = await myPromise;
console.log(data)
}
// this is where i get stuck
const errorHandlerBig = function(fn){ // (1)
return function(...parameters) { // (2)
return fn(...parameters).catch(function(err){ // (3)
console.error('Caught in Example 3:', err)
})
}
}
errorHandlerBig(testPromise)();
Takes testPromise as argument
I debugged this and parameters content
is [] (empty), when trying to assign a param to testPromise =
async(paramName) => .... i can't work with it inside that function.
what is it trying to spread?
this is translated as testPromise(...params from previous step) i assume
Why is this chain of function calling-inside another function necesary?
Following with item (2), when trying to pass a value to paramName, it doesn't get printed on the console either!:
const testPromise = async (paramName) => {
var data = await myPromise;
console.log(data, paramName)
}
const errorHandlerBig = function(fn){...}
errorHandlerBig(testPromise)('RandomValue')
Thank you everyone in advance!

errorHandlerBig is taking a function and wrapping it in its own error handling.
From the outside in, it:
Takes a function(1) as a parameter.
Creates and returns a new function (2) that accepts any number of parameters.
In function 2, calls function 1 using the parameters passed to function 2, and tries to catch any errors generated.
Since errorHandlerBig is passing the parameters to fn, you would need testPromise to accept the parameters and log them if you expected them to appear in your console. Or you could throw them as an error in testPromise.
As written, this isn't very useful, but it could be useful if you needed to make the same async process run more than once by saving the function returned by errorHandlerBig in a variable and calling that with different parameters, or if you wanted to use the same error handling behavior for multiple asynchronous processes.
const throwThings = async (throwWhat) => {
throw new Error(throwWhat);
}
const happyFunTimes = async (activity) => {
console.log("I'm having fun");
await setTimeout(() => console.log(`jumping on a ${activity}`), 1000);
}
const errorHandlerBig = function(fn){ // (1)
return function(...parameters) { // (2)
return fn(...parameters).catch(function(err){ // (3)
console.error('Caught in Example 3:', err.toString())
});
}
}
document.getElementById(":)").addEventListener("click", () => errorHandlerBig(happyFunTimes)("bananas"));
document.getElementById(">:(").addEventListener("click", () => errorHandlerBig(throwThings)("bananas"));
<button id=":)">Fun times</button>
<button id=">:(">Throw</button>

Related

Asyncronicity in a reduce() function WITHOUT using async/await

I am patching the exec() function to allow subpopulating in Mongoose, which is why I am not able to use async/await here -- my function will be chained off a db call, so there is no opportunity to call await on it, and within the submodule itself, there I can't add async/await outside of an async function itself.
With that out of the way, let's look at what I'm trying to do. I have two separate arrays (matchingMealPlanFoods and matchingMealPlanRecipeFoods) full of IDs that I need to populate. Both of them reside on the same array, foods. They each require a db call with aggregation, and the problem in my current scenario is that only one of the arrays populates because they are happening asynchronously.
What I am trying to do now is use the reduce function to return the updated foods array to the next run of reduce so that when the final result is returned, I can replace the entire foods array once on my doc. The problem of course is that my aggregate/exec has not yet returned a value by the time the reduce function goes into its next run. Is there a way I can achieve this without async/await here? I'm including the high-level structure here so you can see what needs to happen, and why using .then() is probably not viable.
EDIT: Updating code with async suggestion
function execute(model, docs, options, lean, cb) {
options = formatOptions(options);
let resolvedCount = 0;
let error = false;
(async () => {
for (let doc of docs) {
let newFoodsArray = [...doc.foods];
for (let option of options) {
const path = option.path.split(".");
// ... various things happen here to prep the data
const aggregationOptions = [
// // $match, then $unwind, then $replaceRoot
];
await rootRefModel
.aggregate(aggregationOptions)
.exec((err, refSubDocuments) => {
// more stuff happens
console.log('newFoodsArray', newFoodsArray); // this is to check whether the second iteration is using the updated newFoods Array
const arrToReturn = newFoodsArray.map((food) => {
const newMatchingArray = food[nests[1]].map((matchingFood) => {
//more stuff
return matchingFood;
});
const updatedFood = food;
updatedFood[`${nests[1]}`] = newMatchingArray;
return updatedFood;
});
console.log('arrToReturn', arrToReturn);
newFoodsArray = [...arrToReturn];
});
}
};
console.log('finalNewFoods', newFoodsArray); // this should log after the other two, but it is logging first.
const document = doc.toObject();
document.foods = newFoodsArray;
if (resolvedCount === options.length) cb(null, [document]);
}
})()
EDIT: Since it seems it will help, here is the what is calling the execute function I have excerpted above.
/**
* This will populate sub refs
* #param {import('mongoose').ModelPopulateOptions[]|
* import('mongoose').ModelPopulateOptions|String[]|String} options
* #returns {Promise}
*/
schema.methods.subPopulate = function (options = null) {
const model = this.constructor;
if (options) {
return new Promise((resolve, reject) => execute(model, [this], options, false, (err, docs) => {
if (err) return reject(err);
return resolve(docs[0]);
}));
}
Promise.resolve();
};
};
We can use async/await just fine here, as long as we remember that async is the same as "returning a Promise" and await is the same as "resolving a Promise's .then or .catch".
So let's turn all those "synchronous but callback-based" calls into awaitables: your outer code has to keep obeying the API contract, but since it's not meant to a return a value, we can safely mark our own version of it as async, and then we can use await in combination with promises around any other callback based function calls in our own code just fine:
async function execute(model, docs, options, lean, andThenContinueToThis) {
options = formatOptions(options);
let option, resolvedCount = 0;
for (let doc of docs) {
let newFoodsArray = [...doc.foods];
for (option of options) {
// ...things happen here...
const aggregationOptions = [/*...data...*/];
try {
const refSubDocuments = await new Promise((resolve, reject) => rootRefModel
.aggregate(aggregationOptions)
.exec((err, result) => err ? reject(err) : resolve(result));
// ...do some work based on refSubDocuments...
}
// remember to forward errors and then stop:
catch (err) {
return andThenContinueToThis(err);
}
}
// remember: bind newFoodsArray somewhere so it doesn't get lost next iteration
}
// As our absolutely last action, when all went well, we trigger the call forwarding:
andThenContinueToThis(null, dataToForward);
}

Leaving jQuery, wrote a simple ajax function, but chained methods will not wait

Update: Added a simpler demonstration jsfiddle, https://jsfiddle.net/47sfj3Lv/3/.
reproducing the problem in much less code I'm trying to move away from jQuery.
Some of my code, for populating some tables, has code like this
var hb = new hbLister(url: '#attributes.listURL#')
.getData(#url.page#, #url.per#)
.search(searchColumn, searchParam)
.render();
hbLister would initialize some things
getData would perform an $.ajax call
search wouldconsole.log('filtering data') and apply the search conditions against a javascript object
render would put the results on the page.
Importantly, search wouldn't fire until after the ajax call in getData finished.
So, now I have this ajax constructor. I've abbreviated as much of this code as I can.
let ajax = function (options, hooks, headers) {
let that = this;
// enforce parameter types
// copy parameters to this.base
this.base = { options: options, hooks: hooks, headers: headers }
return function (url, options, data, hooks, headers) {
// enforce variable types
// merge options and hooks with their base.counterparts
headers = new Headers(Object.assign({}, that.base.headers, headers));
options.headers = headers;
return fetch(url, options)
.then(response => {
return response.json().then(json => {
console.log('processing');
if (response.ok) {
// it's omitted here but the Base functions are defined
// in the constructor parameters
hooks.successBase(json, response.status, response.headers);
hooks.success(response.json, response.status, response.headers)
} else {
hooks.failureBase(json, response.status, response.headers);
hooks.failure(response.json, response.status, response.headers)
}
})
});
}
}
The idea is that I can say
let tableLister = new ajax()
And thengetData can call
tableLister = tableLister(hb.url, // url
{ type: "GET" }, // options
config.data, // data
{ success: successFn } // hooks, success being the callback
)
The jQuery call would properly give me and then processing and then filtering data.
This function gives me filtering data, an error, and thenprocessing, because I cannot seem to get the chain(.search(...).render()) to wait til the ajax call is done.
Here's a self-contained example on jsFiddle, https://jsfiddle.net/47sfj3Lv/3/
I am sure the answer is in async and await, but I have not been able to get it to work.
Here is an example of what I've tried
return await (async function () {
console.log('fetching')
let fetcher = await fetch(url, options);
console.log('getting json');
return await fetcher.json().then((json) => {
console.log('have json, doing hooks');
if (fetcher.ok) {
let argXHR = { json: json}
hooks.successBase(argXHR, hooks.params);
hooks.success.forEach(v => v(argXHR, hooks.params));
hooks.afterSend(argXHR, hooks.params);
} else {
let argXHR = { json: json,}
hooks.failureBase(argXHR, hooks.params);
hooks.failure.forEach(v => v(argXHR, hooks.params));
hooks.afterError(argXHR, hooks.params);
}
console.log('finished hooks')
})
}())
And no matter what I do, the chain, continues before this await finishes..
I got code with XMLHttpRequest to work. The method chain (.getData().search(...).render()) works with this, because this doesn't allow the ajax function to return before the request is finished and callbacks are executed. **I'd still prefer to make .fetch() work.
let xhr = new XMLHttpRequest()
let urlParams = [];
Object.keys(data).forEach((v) => urlParams.push(v + '=' + encodeURIComponent(data[v])));
urlParams = urlParams.join('&')
xhr.open(options.method, options.url, false);
xhr.onreadystatechange = function(state) {
if (this.readyState == 4) {
let json = JSON.parse(xhr.responseText)
hooks.successBase(json, xhr.status, xhr.getResponseHeader);
hooks.success.forEach(v => v(json, xhr.status, xhr.getResponseHeader));
}
}
xhr.onerror = function() {
let json = JSON.parse(xhr.responseText)
hooks.failureBase(json, xhr.status, xhr.getResponseHeader);
hooks.failure.forEach(v => v(json, xhr.status, xhr.getResponseHeader));
}
for (h in headers) {
xhr.setRequestHeader(h, headers[h])
}
xhr.send(urlParams)
This was difficult for me to understand, so I wanted to share if anyone else has the same issue.
It seems that an async method will break a method chain, there's no way around that. And since fetch is asynchronous, await must be used, and in order for await to be used, the calling method must be declared async. Thus the method chain will be broken.
The way the method chain is called must be changed.
In my OP, I linked https://jsfiddle.net/47sfj3Lv/3/ as a much simpler version of the same problem. StackOverflow's 'fiddle' effectively blocks 'fetch' for security reasons, so I need to use JSFiddle for demonstration.
Here's a working version of the same code using then and how/why it works, and a slightly shorter version, because await can be specified with the the fetch, obviously.
let obj = {};
// methods with fetch ideally should be specified async.
// async calls will automatically return a promise
obj.start = async () => {
console.log('start begins')
let retText = "",
fetcher = fetch('/', {}).then(response => response.text())
.then(text => {
console.log('fetch\'s last then')
retText = text
})
// fetcher has just been declared. It hasn't done anything yet.
console.log('fetch requested, returned length:', retText.length)
// this makes the fetcher and sequential then's happen
await fetcher;
console.log('await let fetch finish, returned length:', retText.length)
// Async functions will return a promise, but the return specified here
// will be passed to the next 'then'
return obj
}
obj.middle = () => {
console.log('middle called')
// Because this is not declared async, it behaves normally
return obj;
}
obj.end = () => {
console.log('end called')
// Because this is not declared async, it behaves normally
}
console.log('Method 1: ', obj.start()
// Because start is Async, it returns obj wrapped in a promise.
// As with any function, step may be named Anything you like
.then((step) => step.middle())
// Middle is not Async, but since it's wrapped in then
// it returns a promise
.then((step) => step.end())
)
// This is just wrapped in a timer so the two logs don't intermix with each other
// This is definitely the preferred method. Non-async chain-methods that return
// a reference to their object, do not need to wrapped in then().
setTimeout(function() {
console.log('------------------------')
console.log('Method 2: ', obj.start()
// Because start is Async, it returns obj wrapped in a promise.
// As with any function, step may be named Anything you like
.then((step) => step.middle().end())
)
}, 3000)

asynchronous function does not wait until called asynchronous function was executed in nodejs/javascript

I am calling an asynchronous function which fetches data and subsequently modifies it from another asynchronous function. The latter, however, does not wait until the data was modified in the function called. Currently it is only possible to return the data not anything handled inside the called function since the inside return is not waited for. I tried to async await for the called function but this did not work.
This is the initial call of the function
exports.addRequest = async (req, res) => {
const requestResult = await RequestsModel.addRequestData(req.params.test)
return res.send(requestResult);
};
And this is the function called
exports.addRequestData = async (test) => {
await Requests.findById(requestId)
.then((requestData) =>{
//(01)
if (requestData[type].filter(req => req[compareId] === requestDataObj[compareId]).length !== 0) return "exists";
if (requestData[complementaryType].filter(req => req[complementaryCompareId] === requestDataObj[complementaryCompareId]).length !== 0) return "exists_complementary";
//(02)
requestData[type].push(requestDataObj);
return requestData.save();
}, (err) => {
return err;
});
};
I remove the parameters since they work fine.
The only scenario where I return data is when I return the entire lower mongoose function body. For the promise and data handling the upper function does not seem to wait.
Any help is highly appreciated since I just started working with nodejs and asynchronous functions.
Thanks
Jakob
Try modifying exports.addRequestData like this.
(I agree with Pointy)
exports.addRequestData = async (test) => {
let requestData = await Requests.findById(requestId);
if(requestData.isValid){ // success case
// do operations on requestData and return it
} else { // error case
// return error
}
}
Also there is no mention of requestId. How is that?

How to use a promise with Firestore Firebase

I'm trying to read some data from Firestore using an array of keys and a map function, then, when all the data is back from Firestore, run a function on the resulting data set. This code works, but not in the right order; I'm struggling to make doSomethingUsefulWithData() delay until arrayOfDataFromFirestore is complete.
I've tried await but I get an error message saying "Uncaught syntax error: await is only valid in async functions and async generators". I think the awaits shown below are in the async function but apparently not.
If I run the version shown here, doSomethingUsefulWithData() runs before getData(), despite following it with a .then.
var arrayOfFirestoreKeys = [key0, key1, key3];
function doSomethingUsefulWithData(){
//Do something useful with the data here
};
function dataGet(){
var arrayOfDataFromFirestore = [];
arrayOfDataFromFirestore = Promise.all(arrayOfFirestoreKeys.map(async (dataGet, index) => {
var docRef = db.collection('dataInFirestore').doc(arrayOfFirestoreKeys[index]).get().then((doc) => {
if (doc.exists) {
console.log("Document data from async:", doc.data());
//doSomethingUsefulWithData here works but runs n times for n items in arrayOfFirestoreKeys
};
//Trying await doSomethingUsefulWithData here produces warning 'await must be in async function or function generator'
});
//Trying await doSomethingUsefulWithData here produces warning 'await must be in async function or function generator'
}))
return(arrayOfDataFromFirestore);
};
arrayOfDataFromFirestore = dataGet().then(doSomethingUsefulWithData(arrayOfDataFromFirestore));
//With this version of the code,
//doSomethingUsefulWithData runs first and produces a complaint that arrayOfDataFromFirestore is undefined.
//Then the contents of arrayOfDataFromFirestore follow into console.log
If I correctly understand your question, the following should do the trick:
var arrayOfFirestoreKeys = [key0, key1, key3];
function doSomethingUsefulWithData(arrayOfSnapshots) {
// arrayOfSnapshots is an Array of DocumentSnapshot
arrayOfSnapshots.forEach(docSnap => {
if (docSnap.exists) {
// Do something
}
});
}
function dataGet() {
const arrayOfDataFromFirestore = arrayOfFirestoreKeys.map((k) =>
db.collection('dataInFirestore').doc(k).get()
);
return Promise.all(arrayOfDataFromFirestore);
}
dataGet().then(array => {
doSomethingUsefulWithData(Array);
});

Can't promisify callback based function

I want to use the library astro-js where a typical call in their docs looks like this:
const aztroJs = require("aztro-js");
//Get all horoscope i.e. today's, yesterday's and tomorrow's horoscope
aztroJs.getAllHoroscope(sign, function(res) {
console.log(res);
});
For several reasons, I would like to use it using async/await style and leverage try/catch. So I tried promisify like this:
const aztroJs = require("aztro-js");
const {promisify} = require('util');
const getAllHoroscopeAsync = promisify(aztroJs.getAllHoroscope);
async function handle() {
let result, sign = 'libra';
try {
result = await getAllHoroscopeAsync(sign);
}
catch (err) {
console.log(err);
}
console.log("Result: " + result);
}
However, when I log result it comes as undefined. I know the call worked since the library is automatically logging a response via console.log and I see a proper response in the logs.
How can I "await" on this call? (even by other means if this one is not "promisifyable")
util.promisify() expects the callback function to accept two arguments, the first is an error that must be null when there is no error and non-null when there is an error and the second is the value (if no error). It will only properly promisify a function if the callback follows that specific rule.
To work around that, you will have to manually promisify your function.
// manually promisify
aztroJs.getAllHoroscopePromise = function(sign) {
return new Promise(resolve => {
aztroJs.getAllHoroscope(sign, function(data) {
resolve(data);
});
});
};
// usage
aztroJs.getAllHoroscopePromise(sign).then(results => {
console.log(results);
});
Note, it's unusual for an asynchronous function that returns data not to have a means of returning errors so the aztroJs.getAllHoroscope() interface seems a little suspect in that regard.
In fact, if you look at the code for this function, you can see that it is making a network request using the request() library and then trying to throw in the async callback when errors. That's a completely flawed design since you (as the caller) can't catch exceptions thrown asynchronously. So, this package has no reasonable way of communicating back errors. It is designed poorly.
Try custom promisified function
aztroJs.getAllHoroscope[util.promisify.custom] = (sign) => {
return new Promise((resolve, reject) => {
aztroJs.getAllHoroscope(sign, resolve);
});
};
const getAllHoroscopeAsync = util.promisify(aztroJs.getAllHoroscope);
You could change your getAllHoroscopeAsync to a promise function
Example:
const getAllHoroscopeAsync = (sign) =>
new Promise(resolve =>
aztroJs.getAllHoroscope(sign, (res) => resolve(res)));

Categories

Resources