node, promises, returned values - javascript

I am trying to write a node app that sits on top of memchached sever. The goal is to share some data among several sites, caching some data in memcache.
All the examples I have found using 'get' from memcache return a promise, then does console.log. However, I need the value for returned to the caller of the function. Hence, I loose scope.
Does anybody of an example of doing get as blocking call?
class TPCacheManager{
constructor(){
this.getVal = '';
}
setItem(type,key,item){
var mcacheClient = new MemcachePlus();
mcacheClient.set("TP:IDX","3");
}
getItem(key){
var mcacheClient = new MemcachePlus();
mcacheClient.get("TP:IDX").on((data, status, headers, config) => {
console.log(data.data);
this.getVal= data; // <-- make this value available to the class
});
}
theVal(){
return this.getVal;
}
}

Your question basically translate to:
What is the best way to jump off a sky scraper into a tank full of
sharks with lasers on their heads.
The answer would basically be: "You don't"
Why ECMAScript uses callbacks and promises and async await syntax (is basically promises with a different syntax) is explained here with links to documents and a very good video.
Assuming you are using this memecached client you can set the value member to a promise, the code is kind of confusing since you don't set value when you call setItem. GetItem sets value but I think it's better if you name it lastRetreivedValue (and maybe add lastSetItem depending on your needs):
class TPCacheManager{
constructor(){
this.lastRetrievedValue = Promise.reject("No item has been set");
}
setItem(type,key,item){
var mcacheClient = new MemcachePlus();
//the caller may want to know when it's finished and if it failed
return mcacheClient.set("TP:IDX","3");
}
getItem(key){
var mcacheClient = new MemcachePlus();
const me = this;
//caller may want to know when it's finished and if it failed
return mcacheClient.get("TP:IDX").
then(value => {
console.log(value);
me.lastRetrievedValue= value;
//you may want to return value here so o.getItem().then(value
//actually resolves to something other than undefinded
});
}
theVal(){
return this.lastRetrievedValue;
}
}
//example how to use:
const cache = new TPCacheManager();
cache.lastRetrievedValue.catch(
err=>{
console.log("As expected, no value has been set",err);
}
);
cache.setItem("doesnt matter, you ignore all parameters here")
.catch(err=>console.error("Oops, something went wrong setting an item:",err));
cache.lastRetrievedValue
.catch(
err=>
console.log("Value still not set, setItem does not do this in your code",err)
);
cache.getItem("doesnt matter, you are ignoring parameters here")
.then(
value=>
console.log("Value is undefined because getItem resolves to undefined",value)
);
cache.lastRetrievedValue
.then(
value=>
console.log("Ok, I have value because getItem has set it:",value)
);
//you can repeat cache.lastRetrievedValueor cache.theVal() without connecting to
// memcached because you stored the promise

Related

NodeJS: Chain functions automatically in a promise?

I'm currently fetching data from an API and I need to do multiple GET requests (using axios). After all those GET requests are completed, I return a resolved promise.
However, I need to do these GET requests automatically based on an array list:
function do_api_get_requests() {
return promise = new Promise(function(resolve, reject) {
API_IDs = [0, 1, 2];
axios.get('https://my.api.com/' + API_IDs[0])
.then(data => {
// Do something with data
axios.get('https://my.api.com/' + API_IDs[1])
.then(data => {
// Do something with data
axios.get('https://my.api.com/' + API_IDs[2])
.then(data => {
// Do something with data
// Finished, resolve
resolve("success");
}
}
}
}
}
This works but the problem is API_IDs isn't always going to be the same array, it will change. So I'm not sure how to chain these requests automatically.
Since you said it may be a variable length array and you show sequencing the requests, you can just loop through the array using async/await:
async function do_api_get_requests(API_IDS) {
for (let id of API_IDS) {
const data = await axios.get(`https://my.api.com/${id}`);
// do something with data here
}
return "success";
}
And, since you said the list of API ids would be variable, I made it a parameter that you can pass into the function.
If you wanted to run all the API requests in parallel (which might be OK for a small array, but might be trouble for a large array) and you don't need to run them in a specific order, you can do this:
function do_api_get_requests(API_IDS) {
return Promise.all(API_IDS.map(async (id) => {
const data = await axios.get(`https://my.api.com/${id}`);
// do something with data here for this request
})).then(() => {
// make resolved value be "success"
return "success";
});
}
Depending upon your circumstances, you could also use Promise.allSettled(). Since you don't show getting results back, it's not clear whether that would be useful or not.
You can use Promise.all() method to do all API requests at the same time, and resolve when all of them resolves.
function do_api_get_requests() {
const API_IDs = [0, 1, 2];
let promises = [];
for (const id of API_IDS) {
promises.push(axios.get(`https://my.api.com/${id}`));
}
return Promise.all(promises);
}
If you use Bluebird.js (a better promise library, and faster than the in-built Promise), you can use Promise.each(), Promise.mapSeries(), or Promisme.reduce() to do what you want.
http://bluebirdjs.com

Problem with Promises and code timeout when inserting data into a collection

I am trying to verify if a object already exists in a wix collection and if it does cancel the inset() call to the database
import wixData from "wix-data";
export function Memberships_beforeInsert(item, context) {
var name = item.firstName + item.lastName;
name = name.toLowerCase();
wixData.query(context.collectionName)
.find()
.then((res) => {
var members = res.items;
var len = res.length;
console.log(len)
for (var i = 0; i < len; i++) {
let member = members[i];
let memberName = member.firstName + member.lastName;
memberName = memberName.toLowerCase();
if (memberName === name) {
let toUpdate = {
'_id': member._id
}
wixData.update(context.collectionName, toUpdate)
return null;
}
return item;
}
});
//toHere
}
Im fairly new to and wixCode but I was expecting this to wait until the .then() is called then return as follows, but due to wixCode utilizing promises, the code goes immediately to the //toHere section of the code in which it dosent find a return and dismisses the call. Which adds the data to the database instead of returning null.
OK so when we look at your code it seems that you want to find a matching record to the one you are trying to insert to Memberships and then abort the insert and execute an update if one exists. The better way to do this is to look for the specific record match using the query .eq() function. If this finds a matching record then you can update otherwise continue with the insert. See below.
So quickly what is a promise?
In layman's terms think of a Promise like a FedEx tracking code.
When you call a promise based function to ask it to do something you are given back a Promise that what you asked for will be done or that you will be told if a problem occurred.
Just like a fed ex tracking code - you don't know if something has arrived (your function has done what you want it to) unless you check the tracking status using the code OR the fedex delivery van arrives and you receive the package. At which point the tracking code is updated to say the package was delivered and you might get a text or email saying it has been delivered.
A Promise function either completes successfully and you get a result in the then function call OR if an error occurs a catch() event is triggered. So All Promise based functions are similar to if then else conditional test only they look like this:
sendFedEx(package) // Call doesn't resolve immediately you have to wait for fedEx!
.then((fedExDeliveryInfo) => {
//Your package arrived!
})
.catch((fedExLostPackageInfo) => {
//Your package got lost :-(
});
In your code you do a case insensitive compare outside of the data collection. This is probably going to get resource intensive with a large data collection. A better way would be to store the case insensitive string:
(item.firstName + item.lastName).toLowerCase() in the data record and then use it for your query using .eq. That way you let the data collection do its job and simplify your code.
Note: This makes use of the fact that beforeInsert() returns a Promise.
Syntax:
function beforeInsert(item: Object, context: HookContext): Promise
Here is a modified suggestion for you with lots of comments!
import wixData from "wix-data";
export function Memberships_beforeInsert(item, context) {
// Memberships_beforeInsert returns a promise. So does
// wixData.query...find() so we simply return it to maintain the Promise
// chain.
var compareName = (item.firstName + item.lastName).toLowerCase();
// Add a compareName column value to item for future querying
// This will be inserted into the data collection
item.compareName = compareName;
//-------------------------------------------------------//
// This is the head of the Promise chain we need to return
//-------------------------------------------------------//
return wixData.query(context.collectionName)
.eq('compareName', item.compareName) // Query the compareName
.find()
.then((res) => {
var members = res.items;
var len = res.length;
console.log(len);
// Should only have one record or no records otherwise we have a
// problem with this code :-)
// So if we have too many we throw and error. This will be caught in
// an outer catch if we have one
if (len > 1) {
throw Error(`Internal Error! Too many records for ${item.firstName} ${item.lastName}`);
}
// If we get here we have a valid record OR we need to return a result
// To do this we will use a return variable set to the item which
// assumes we will insert item. This will be overridden with the save
// Promise if we have a record already
var result = item;
if (len === 1) {
// We have a record already so we need to update it and return null
// to the caller of beforeInsert to halt the insert. This is a
// Simple case of adding the _id of the found record to the item we
// have been given.
item['_id'] = member._id;
// Again remember we are using promises so we need to return the
// wixData.update result which is a promise.
result = wixData.update(context.collectionName, toUpdate)
.then((savedRecord) => {
// Now we have chained the update to the beforeInsert response
// This is where we can tell the Insert function to abort by
// returning null.
return null;
});
}
// Now we can return the result we have determined to the caller
return result;
});
}
This should do what you are trying to accomplish.

Promises messing up?

This issue has been driving me nuts for the last couple of days. I'm far from being a Javascript expert, maybe the solution is obvious but I don't see it.
What I basically try to do is :
download items in paralell, each request being made for a given
item type (type1, type2) with different properties.
Once downloaded, execute a callback function to post-process the data
(this is the same function with different parameters and with a test
on the item type in order to have different processing)
Save the item
If I download 1 item type, everything is OK. But if I download 2 types, then at some point in the processing loop within the first callback execution, exactly when the callback is executed a 2nd time for the 2nd type, then the test on type will indicate it's the 2nd type, while the items are of the 1st type...
Here is an extract of the code :
downloadType1();
downloadType2();
// 2nd argument of download() is the callback function
// 3rd argument is the callback function parameters
function downloadType1() {
// Some stuff here
let callbackParameters = ['type1', 'directory1'];
download('url', headacheCallback, callbackParameters);
}
function downloadType2() {
// Some the stuff here
let callbackParameters = ['type2', 'directory2'];
download('url', headacheCallback, callbackParameters);
}
async function download(url, callbackBeforeSave, callbackParameters) {
// Some stuff here
let response;
try {
response = await rp(url);
} catch (e) {
console.log("Error downloading data");
}
// Call callbacks before saving the data
if(callbackBeforeSave) {
let updatedResponse;
if (callbackParameters) {
updatedResponse = await callbackBeforeSave(response, ...callbackParameters);
} else {
updatedResponse = await callbackBeforeSave(response);
}
response = updatedResponse;
}
// Some stuff here with the post-processed data
}
async function headacheCallback(data, type, directory) {
for (item of data) {
// Some stuff here, include async/await calls (mostly to download and save files)
console.log(type, item.propertyToUpdate, item.child.propertyToUpdate);
// This is were my issue is.
// The test will success although I'm still the 'type1' loop. I know because the console.log above shows the item is indeed of type 'type1'
if (type === 'type2') {
item.child.propertyToUpdate = newUrl; // Will eventually fail because 'type1' items don't have a .child.propertyToUpdate property
} else {
item.propertyToUpdate = newUrl;
}
}
}
At some point, the output of console.log will be :
type2 <valueOfTheProperty> undefined which should be type2 undefined <valueOfTheProperty>...
A quick thought : in the first version of the callback, I used the arguments global variables in combination with function.apply(...). This was bad precisely because arguments was global and thus was changed after the 2nd call...
But now I don't see anything global in my code that could explain why type is changing.
Any help would be greatly appreciated.
Thanks!
I don't see anything global in my code that could explain why type is changing.
It's not type that is changing. Your problem is item that is an involuntary global:
for (item of data) {
// ^^^^
Make that a
for (const item of data) {
// ^^^^
And always enable strict mode!
This is a job for Promise.all
const p1 = new Promise((res, rej) => res());
Promise.all([p1, p2]).then((results) => results.map(yourFunction));
Promise.all will return an array of resolved or may catch on any rejection. But you don't have to reject if you setup your p1, p2, pn with a new Promise that only resolves. Then your function map can handle the branching and do the right thing for the right response type. Make sense?

Get undefined when returning a value from an array - Node JS

I'm new to NodeJS and I get some difficulties with its asynchronous nature.
I'm requesting some data using a async function. My second function is used to retrieve an ID while knowing the name (both info are stored in the data returned by the first function).
Everytime I get the 'Found it' in the console, but the return is executed before the loop is over and I get an 'undefined'.
Should I use a callback or use async & await ? Even after lot of research about async & await and callbacks I can't figure a way to make it work !
async function getCustomers() {
try {
var customers = await axios({
//Query parameters
});
return customers;
}
catch (error) {
console.log(error);
}
}
function getCustomerId(customerName){
var customerId = null;
getCustomers().then(function(response){
for (const i of response.data){
console.log(i['name']);
if(i['name'] == customerName){
console.log('Found it !'); //This got displayed in the console
customerId = i['id'];
return customerId; //This never return the desired value
}
}
});
}
console.log(getCustomerId('abcd'));
Thanks for any help provided !
You're printing the output of getCustomerId, but it doesn't return anything.
Try returning the Promise with:
return getCustomers().then(function(response) {...});
And then, instead of:
console.log(getCustomerId('abcd'));
You should try:
getCustomerId('abcd').then(function(id) {console.log(id);})
So that you are sure that the Promise is resolved before trying to display its output

Node.js for loop using previous values?

Currently, I am trying to get the md5 of every value in array. Essentially, I loop over every value and then hash it, as such.
var crypto = require('crypto');
function userHash(userIDstring) {
return crypto.createHash('md5').update(userIDstring).digest('hex');
}
for (var userID in watching) {
refPromises.push(admin.database().ref('notifications/'+ userID).once('value', (snapshot) => {
if (snapshot.exists()) {
const userHashString = userHash(userID)
console.log(userHashString.toUpperCase() + "this is the hashed string")
if (userHashString.toUpperCase() === poster){
return console.log("this is the poster")
}
else {
..
}
}
else {
return null
}
})
)}
However, this leads to two problems. The first is that I am receiving the error warning "Don't make functions within a loop". The second problem is that the hashes are all returning the same. Even though every userID is unique, the userHashString is printing out the same value for every user in the console log, as if it is just using the first userID, getting the hash for it, and then printing it out every time.
Update LATEST :
exports.sendNotificationForPost = functions.firestore
.document('posts/{posts}').onCreate((snap, context) => {
const value = snap.data()
const watching = value.watchedBy
const poster = value.poster
const postContentNotification = value.post
const refPromises = []
var crypto = require('crypto');
function userHash(userIDstring) {
return crypto.createHash('md5').update(userIDstring).digest('hex');
}
for (let userID in watching) {
refPromises.push(admin.database().ref('notifications/'+ userID).once('value', (snapshot) => {
if (snapshot.exists()) {
const userHashString = userHash(userID)
if (userHashString.toUpperCase() === poster){
return null
}
else {
const payload = {
notification: {
title: "Someone posted something!",
body: postContentNotification,
sound: 'default'
}
};
return admin.messaging().sendToDevice(snapshot.val(), payload)
}
}
else {
return null
}
})
)}
return Promise.all(refPromises);
});
You have a couple issues going on here. First, you have a non-blocking asynchronous operation inside a loop. You need to fully understand what that means. Your loop runs to completion starting a bunch of non-blocking, asynchronous operations. Then, when the loop finished, one by one your asynchronous operations finish. That is why your loop variable userID is sitting on the wrong value. It's on the terminal value when all your async callbacks get called.
You can see a discussion of the loop variable issue here with several options for addressing that:
Asynchronous Process inside a javascript for loop
Second, you also need a way to know when all your asynchronous operations are done. It's kind of like you sent off 20 carrier pigeons with no idea when they will all bring you back some message (in any random order), so you need a way to know when all of them have come back.
To know when all your async operations are done, there are a bunch of different approaches. The "modern design" and the future of the Javascript language would be to use promises to represent your asynchronous operations and to use Promise.all() to track them, keep the results in order, notify you when they are all done and propagate any error that might occur.
Here's a cleaned-up version of your code:
const crypto = require('crypto');
exports.sendNotificationForPost = functions.firestore.document('posts/{posts}').onCreate((snap, context) => {
const value = snap.data();
const watching = value.watchedBy;
const poster = value.poster;
const postContentNotification = value.post;
function userHash(userIDstring) {
return crypto.createHash('md5').update(userIDstring).digest('hex');
}
return Promise.all(Object.keys(watching).map(userID => {
return admin.database().ref('notifications/' + userID).once('value').then(snapshot => {
if (snapshot.exists()) {
const userHashString = userHash(userID);
if (userHashString.toUpperCase() === poster) {
// user is same as poster, don't send to them
return {response: null, user: userID, poster: true};
} else {
const payload = {
notification: {
title: "Someone posted something!",
body: postContentNotification,
sound: 'default'
}
};
return admin.messaging().sendToDevice(snapshot.val(), payload).then(response => {
return {response, user: userID};
}).catch(err => {
console.log("err in sendToDevice", err);
// if you want further processing to stop if there's a sendToDevice error, then
// uncomment the throw err line and remove the lines after it.
// Otherwise, the error is logged and returned, but then ignored
// so other processing continues
// throw err
// when return value is an object with err property, caller can see
// that that particular sendToDevice failed, can see the userID and the error
return {err, user: userID};
});
}
} else {
return {response: null, user: userID};
}
});
}));
});
Changes:
Move require() out of the loop. No reason to call it multiple times.
Use .map() to collect the array of promises for Promise.all().
Use Object.keys() to get an array of userIDs from the object keys so we can then use .map() on it.
Use .then() with .once().
Log sendToDevice() error.
Use Promise.all() to track when all the promises are done
Make sure all promise return paths return an object with some common properties so the caller can get a full look at what happened for each user
These are not two problems: the warning you get is trying to help you solve the second problem you noticed.
And the problem is: in Javascript, only functions create separate scopes - every function you define inside a loop - uses the same scope. And that means they don't get their own copies of the relevant loop variables, they share a single reference (which, by the time the first promise is resolved, will be equal to the last element of the array).
Just replace for with .forEach.

Categories