How can I implement memoize method on an async function in JavaScript? - javascript

I have been trying to write the implementation of the memoize function in JavaScript. I was asked this in an interview question and haven't been able to put it out of my mind since. I would really appreciate some help with this.
Given a function in which we are making an API call -
function getSomeData(foo, callback) {
var uri = 'http://localhost?foo=' + foo ;
someAPIUri(uri, function onResponse(error, response, body) {
callback(error, body);
});
}
Instead of using an API, using setTimeout for async functionality -
function getSomeData(foo, callback) {
setTimeout(()=> {
console.log('async request');
callback(2 * foo);
}, 1000);
}
If we make a call like below twice, that means two async calls made, so we need to create a memoize function which caches the response for some input and respond with that in any subsequent call.
getSomeData(1, (response) => {
console.log('getSomeData', response);
})
I wrote this function -
function memoize(fn) {
const cache = {};
return async function() {
const args = JSON.stringify(arguments);
console.log('arguments passed to memoize fn: ', args);
console.log('cache data: ', cache[args]);
cache[args] = cache[args] || fn.apply(undefined, arguments);
return cache[args]
}
}
const memoizedGetSomeData = memoize(getSomeData);
const callback_fn = (response) => {
console.log('callback response: ', response);
}
memoizedGetSomeData(1, callback_fn);
memoizedGetSomeData(1, callback_fn);
This is not working as the async call is getting made for each memoizedGetSomeData call.
I would really appreciate some input to get it working and improving my understanding.
This is a codepen of the code - link
The console log:
"arguments passed to memoize fn: " "{'0':1}"
"cache data: " undefined
"arguments passed to memoize fn: " "{'0':1}"
"cache data: " undefined
"async request"
"callback response: " 2
"async request"
"callback response: " 2

The only real issue (from what I see) is that getSomeData is a poor mock of async functionality, as it doesn't return a promise.
function getSomeData(foo, callback) {
return new Promise((resolve, reject) => {
setTimeout(()=> {
console.log('async request');
resolve(callback(2 * foo));
}, 1000);
});
}
function memoize(fn) {
const cache = {};
return async function() {
const args = JSON.stringify(arguments);
console.log('arguments passed to memoize fn: ', args);
console.log('cache data: ', cache);
cache[args] = cache[args] || fn.apply(undefined, arguments);
return cache[args]
}
}
const memoizedGetSomeData = memoize(getSomeData);
const callback_fn = (response) => {
console.log('callback response: ', response);
}
memoizedGetSomeData(1, callback_fn);
memoizedGetSomeData(1, callback_fn);

Thanks #Bergi and #dave for the comments. I understood the problem with my code and how the memoize function should work like for an async function.
This is the final code with a basic implementation of Memoize on an async function -
function getSomeData(foo, callback) {
return new Promise((resolve, reject) => {
setTimeout(()=> {
console.log('async request');
resolve(callback(2 * foo));
}, 1000);
});
}
function memoize(fn) {
const cache = {};
return async function() {
const args = JSON.stringify(arguments);
cache[args] = cache[args] || fn.apply(undefined, arguments);
return cache[args]
}
}
const memoizedGetSomeData = memoize(getSomeData);
const callback_fn = (response) => {
return response;
}
// this should make async call
memoizedGetSomeData(1, callback_fn).then(response => console.log('response from async call: ', response));
// this should return response from the cache
memoizedGetSomeData(1, callback_fn).then(response => console.log('cached response: ', response));
// this should make async call (different argument)
memoizedGetSomeData(4, callback_fn).then(response => console.log('response from async call: ', response));
Log:
"async request"
"response from async call: " 2
"cached response: " 2
"async request"
"response from async call: " 8
I have updated the Codepen code as well.

function getSomeData(foo, callback) {
console.log('inside async call', foo)
setTimeout(() => callback(foo * Math.random(100)), 1000)
}
function memoize(fn) {
let cache = {};
return function() {
let key = JSON.stringify(arguments);
if (key in cache) {
return cache[key];
} else {
cache[key] = fn.apply(this, arguments);
return cache[key];
}
};
}
getSomeDatav2 = memoize(getSomeData);
getSomeDatav2(2, (r) => console.log(r));
getSomeDatav2(2, (r) => console.log(r));
getSomeDatav2(2, (r) => console.log(r));
getSomeDatav2(1, (r) => console.log(r));
getSomeDatav2(1, (r) => console.log(r));
getSomeDatav2(3, (r) => console.log(r));

Related

Second function not called asynchronously in nodejs

I am trying to call some function using a single express router , I want to call them in order, meaning that I don't want getLaps() function to execute before get streams function has done all the work , so I tried to use some solutions I found on the internet but it didn't work, the second function doesn't execute. Please help.
Here is my code :
router.get("/", async (req, res,done) => {
res.status(201).send('created user')
return getLaps(function () {
getStreams(function () {
});
});
// await getStreams();
// await getLaps();
// console.log("hey")
});
Here is the get laps function :
function getLaps(req) {
const access_token = '75f2d92fdc445033312854d775e039b6c5bf04e7';
//for test 3756582581,
const idL = [5567017025, 5566531480];
const stravaClient = StravaClientService.getClient(access_token);
const activityService = StravaActivityService(stravaClient);
var params = {
TableName: "run-id",
Key: {
"id": "15428785",
}
};
console.log("cool laps")
docClient.get(params, async function (err, data) {
if (err) {
console.log("Error", err);
} else {
}
idL.map((id, index) => setTimeout(() => activityService.listLaps(id), (5 + index) * 60)
)
//data.Item.json
});
}
and the streams function :
function getStreams(req) {
const idS = [
5567017025, 5566531480
];
const stravaClient = StravaClientService.getClient(access_token);
const activityService = StravaActivityService(stravaClient);
var params = {
TableName: "run-id",
Key: {
"id": "15428785",
}
};
console.log("cool streams")
docClient.get(params, async function (err, data) {
if (err) {
console.log("Error", err);
} else {
idS.map((id, index) => setTimeout(() => activityService.streamActivity(id), (5 + index) * 60))
console.log("got the streams")
}
});
}
in your getStream and getLaps function return promises instead of other object/Stuff like
async function getStream(){
return new Promise(async (resolve, reject){
//Do something
//where you want to return something just call resolve function like
resolve()
//if you want some output of getStream() just pass it to resolve function
//const result = 'I'm result'
resolve(result)
})
}
do same thing with the laps function and in your router call them with await keyword

Can not return from a function

I have a function that looks like following
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
return true;
} else {
console.log("studio not available");
return false;
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
};
however the return statements don't return anything when I use the function in the following manner
useEffect(() => {
const agent = checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("studio available is: ", agent);
}, []);
the console.log massages appear but the return statement is undefined.
any help would be appreciated.
You can not return from a callback function, as it is running asynchronously and you are not waiting for it to have a result ready.
You can however make the function itself async by returning a Promise instead of the actual result and wait until the Promise has a result ready (e.g. it is resolved):
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
reject(); // reject on failure
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
resolve(true); // resolve instead of return
} else {
console.log("studio not available");
resolve(false);
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
).then((agent) => { // then callback is called when the promise resolved
console.log("studio available is: ", agent);
}).catch(error => { // catch is called when promise got rejected
console.log('An error happened');
});
}, []);
The function servceInfo.OnServiceStateChange is a function into the object (seems to be an event).
I'd suggest declaring a variable on the checkForAvailableAgent like connected and change it's value when the event is called.
Then access it using checkForAvailableAgent.connected.
A version with async/await and try/catch
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId,
OnError: reject,
OnServiceStateChange: e => resolve(e.ConnectedAdvisers > 0)
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
(async () => {
try {
const isAvailable = await checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("Result", isAvailable)
} catch(e) {
console.error(e)
}
})()
// console.log("studio available is: ", agent);
}, []);
There are 2 possible reasons
you are not returning anything from checkForAvailableAgent.
After returning from the checkForAvailableAgent, it might be asynchronous function. You can use async & await.

Combine two callbacks into one return

So I have this code:
module.exports.getEstimate = (event, context, callback) => {
var data = JSON.parse(event.body);
lalamove.getQuotation(data ,context, function(err, llm_data){
callback(null,llm_data)
});
};
So it calls lalamove.getQuotation function and returns an object:
{ "totalFee": "108", "totalFeeCurrency": "PHP" }
Now, I have added a new function, that returns this object:
{ "totalFee": "10", "totalFeeCurrency": "PHP" }
from a different function so I thought I should push them in one array and then that is when I would call the callback but it does not work, this is what I have tried
module.exports.getEstimate = (event, context, callback) => {
var data = JSON.parse(event.body);
var response = []
lalamove.getQuotation(data ,context, function(err, llm_data){
const llm_obj = { "lalamove": llm_data }
response.push(llm_obj);
});
inhouse.getQuotation(data ,context, function(err, ih_data){
const ih_obj = {"inhouse": ih_data }
response.push(ih_obj);
});
callback(null,response);
};
and what I want to be the response is like this:
["lalamove": { "totalFee": "108", "totalFeeCurrency": "PHP" },
"inhouse": { "totalFee": "10", "totalFeeCurrency": "PHP" }]
what am I doing wrong?
Your callback(null,response) will not wait for those two callback functions to finish. You can use Promise and use Promise.all(objs).then(function) to wait for all promises finish and run.
Welcome to World's Javascript world - Callback hell.
We have some options for your case: Callback hell, async lib, Promise, async/await...
Callback hell: Call a async function in a callback
module.exports.getEstimate = (event, context, callback) => {
var data = JSON.parse(event.body);
var response = []
lalamove.getQuotation(data, context, function (err, llm_data) {
const llm_obj = { "lalamove": llm_data }
response.push(llm_obj);
// lalamove.getQuotation done!
// call next action
inhouse.getQuotation(data, context, function (err, ih_data) {
const ih_obj = { "inhouse": ih_data }
response.push(ih_obj);
// inhouse.getQuotation done!
// call the last action
callback(null, response);
});
});
};
Async lib: async
You can use waterfall function to do actions in order, and parallel if order is not matter.
module.exports.getEstimate = (event, context, callback) => {
var data = JSON.parse(event.body);
var response = []
async.parallel([
function (next) {
lalamove.getQuotation(data, context, function (err, llm_data) {
// TODO: check err object
const llm_obj = { "lalamove": llm_data }
response.push(llm_obj);
// lalamove.getQuotation done!
// do next action
next();
});
},
function (next) {
inhouse.getQuotation(data, context, function (err, ih_data) {
const ih_obj = { "inhouse": ih_data }
response.push(ih_obj);
// inhouse.getQuotation done!
// do next action
next()
});
}
], function (err) {
// TODO: check err object
// call the last action
callback(null, response);
});
};
Try wrapping two quotation calls in Promise, then utilise Promise.all to wait for both of them to be completed, then return the result to the callback
module.exports.getEstimate = (event, context, callback) => {
let data = JSON.parse(event.body);
// wrap quotation calls in `Promise`
Promise.all([
new Promise(resolve => lalamove.getQuotation(data, context, (err, lalamove) => resolve({ lalamove }))),
new Promise(resolve => inhouse.getQuotation (data, context, (err, inhouse ) => resolve({ inhouse }))),
]).then(response => {
// return the result back to `callback`
callback(null, response);
})
};
You could also try using util.promisify and the async / await syntax.
For example:
const util = require("util");
module.exports.getEstimate = async (event, context, callback) => {
let data = JSON.parse(event.body);
try {
let response = await Promise.all([ util.promisify(lalamove.getQuotation)(data, context),
util.promisify(inhouse.getQuotation)(data, context) ]);
callback(null, response);
} catch (err) {
callback(err);
}
};
We can also do something similar, but without async / await:
const util = require("util");
const getEstimate = (event, context, callback) => {
let data = JSON.parse(event.body);
Promise.all([util.promisify(lalamove.getQuotation)(data, context),
util.promisify(inhouse.getQuotation)(data, context)])
.then(response => callback(null, response))
.catch(err => callback(err));
};

Async/Await function not waiting for promise to end

let myObject = ( () => {
let run = async() => {
foo = new genericFunction();
status = await foo.myFunction();
}
})();
Another FIle.js
let genericFunction = function() {
this.getData = async () => {
$.getJSON("path/to/file.json", function (data) {
console.log("Apple", data.name);
return data.name;
}).fail(function(jqxhr, textStatus, error){
console.log("Loading Error :: ", error);
)};
}
this.myFunction = async () => {
let data = this.getData();
console.log('DATAA:::', data); //This should be the first output
}
}
The problem is:
status is always = undefined because somehow it returns before getJSON executes and I don't know why.
Another FIle.js should be like:
let genericFunction = function() {
this.getData = async () => {
var result = await $.getJSON("path/to/file.json", function (data) {
console.log("Apple", data.name);
return data.name;
}).catch(function(jqxhr, textStatus, error){
console.log("Loading Error :: ", error);
)};
return result;
}
this.myFunction = async () => {
let data = this.getData();
console.log('DATAA:::', data); //This should be the first output
}
}
Here's a simplified example using your code but with a couple of changes:
1) Uses a class
2) You're not returning anything from myFunction so there's no reason to assign something to status - just call the function.
3) getData doesn't need an async keyword, and you need to return the promise (in this example I mocked up a AJAX call that returns data after a second) from the function for it to work.
4) You do need to await for the promise to return in myFunction. You have async there, you just need to add the await keyword too.
class GenericFunction {
getData() {
return new Promise(resolve => {
setTimeout(() => resolve('Hello World'), 2000);
});
}
async myFunction() {
let data = await this.getData();
console.log('DATAA:::', data);
}
}
(async () => {
const foo = new GenericFunction();
foo.myFunction();
})();

how to trigger a async process one after another

How I should modify the following code, so I can make sure Process3 is triggered after Process2.update or Process2.create completed?
The main purpose for following code is I want to makeProcess1 finished. Then check if id exist, if yes, Process2.update is triggered. if not, Process2.create is triggered.Once Process2 finished, check if cmd existed. if yes,triggered Process3.
run: function (req, res) {
if (req.session) {
const values = req.params.all();
const id = values.id;
const cmd = values.cmd;
const param = _.omit(values, ['cmd', 'id']);
const cb1 = (e, d) => {
if (e) {
console.log(e);
res.status(400).send({ e });
} else {
Process1(values);
res.status(200).send({ d });
}
};
const cd2 = (id, param, cb1) => {
if (id) {
Process2.update({ id }, param, cb1);
} else {
Process2.create(param, cb1);
}
};
if (cmd) {
cd2(id, param, cb1, Process3(values, cmd));
}
else {
cd2(id, param, cb1);
}
} else {
res.status(403).send({ e: 'Forbidden access.' });
}
}
try approach by following, but not sure how I can pass argument id, params to Process2 and process3
let async = require('async');
const Process1 = (value, cb) => {
console.log("Process1()");
console.log(value);
cb(null, value + 1);
};
const Process2 = (value, cb) => {
console.log("value(): wait 5 sec");
console.log(value);
cb(null, value+10);
};
const Process3 = (value, cb) => {
console.log(value);
console.log("Process3(): wait 5 sec");
cb(null, value+100);
};
let Pro_1_2 = async.compose(Process2, Process1);
let Pro_2_3 = async.compose(Process3, Process2);
Pro_1_2(1, (e, r) => {
Pro_2_3(r, (error, result) => {
console.log(result);
});
});
The code you posted in your original question seems pretty twisted up, so I'm not going to attempt to rewrite it, but in general if you want to perform asynchronous calls which depend on each other, async.auto is a good way to go. Rather than declaring variables at the top that you attempt to mutate via some function calls, it's better to make Process1, Process2 and Process3 asynchronous functions that call their callbacks with a new values object. Something like:
async.auto({
doProcess1: function(cb) {
// Assuming Process1 calls `cb(undefined, newValues)` when done.
Process1(values, cb);
return;
},
doProcess2: ['doProcess1', function(results, cb) {
if (results.doProcess1.id) {
Process2.update({id: results.doProcess1.id}, cb);
return;
} else {
Process2.create(_.omit(results.doProcess1, ['cmd', 'id']), cb);
return;
}
}],
doProcess3: ['doProcess2', function(results, cb) {
if (results.doProcess2.cmd) {
Process3(results.doProcess2, cb);
return;
}
else {
cb(undefined, results.process2);
return;
}
}]
}, function afterProcess3(err, results) {
// Handler err or process final results.
});
Note all the return calls. They're not strictly necessary, but good practice to avoid accidentally running more code after calling your asynchronous functions.
Have you considered using "compose", from async.js?
const a = (data, cb) => {
var result = 'a';
cb(null, result);
};
const b = (data, id, cb) => {
var result = 'b';
cb(null, result);
};
const c = (data, cb) => {
// stuff to do with result
};
var aThenC = async.compose(c, a);
var bThenC = async.compose(c, b);
if (useA) {
aThenC(data, (result) => {
// result from c
res.status(200).send(result);
});
} else {
bThenC(data, id, (result) => {
// result from c
res.status(200).send(result);
});
}
In this scenario, a and b are your Process2 create and update, respectively, and c is the callback to Process3, if I understood correctly.
EDIT: You'll only have to enter the initial parameters (e.g. register ID) on the composed function. What composes really do is this: a(b(c(param))). That param is basically everything you need to start the process. The parameters for the following functions will be set inside the function before that.
I'll add code to support it as soon as I'm on a keyboard.

Categories