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?
Related
I'm working on switching the code to rxjs
here is my original code.
userAuth$: BehaviorSubject<ArticleInfoRes>;
async loadArticleList(articleId: number) {
try {
const data = await this.articleApi.loadArticleList(articleId);
this.userAuth$.next(data);
return data;
} catch (error) {
return error;
}
}
Subsequently, an attempt was made to convert to rxjs, but the value was not delivered properly. A value or error must be passed to the location where the loadArticleList function is used. Please tell me what's wrong
This is the code I tried to convert.
userAuth$: BehaviorSubject<ArticleInfoRes>;
loadArticleList(articleId: number) {
from(this.articleApi.loadArticleList(articleId)).subscribe(
data => {
this.userAuth$.next(data);
return data;
},
error => {
return error;
}
)
}
A value or error must be passed to the location where the loadArticleList function is used. Please tell me what's wrong
To answer your question directly, the reason why the result (i.e., value or error) isn't passed to where you use loadArticleList() is because your RxJS pipeline is not built properly. For one, adding return data; and return error; in your subscription handlers won't actually return data nor error.
To get the result, you might want to "trickle down" (streamline) that result further down the RxJS pipeline, so that subscription to that result happens in the location you speak of, to where loadArticleList() is used. This translates to moving your call to .subscribe() outside of loadArticleList(), not inside.
So, here's a proper RxJS revision:
import { tap } from "rxjs/operators";
userAuth$: BehaviorSubject<ArticleInfoRes>;
loadArticleList(articleId: number) {
return from(this.articleApi.loadArticleList(articleId)).pipe(
tap((data) => this.userAuth$.next(data)) // 👈 tap into the result to update the next userAuth$
);
}
Using the tap RxJS operator, we can grab the result of loadAarticleList(articleId), process it according to your callback function inside tap (in this case, simply update the next value of userAuth$), and then with that same grabbed result, unmodified, pass it on to whoever subscribes to loadArticleList().
Finally, to actually "pass" data or errorto the location where the loadArticleList function is used, simply subscribe there.
// in your component's .ts somwehere, probably
loadArticleList(123).subscribe(
data => { /* do as you want with the result */ },
error => { /* add your error-handling logic here */ }
);
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.
I'm trying to learn pomise and I used code which I found in Internet... I don't understand everything. It looks for me very nasty but it works...
I initialized promise
function initialize(city) {
var options = {
url: 'https://api.weatherbit.io/v2.0//forecast/',
headers: {
'User-Agent': 'request'
}
};
return new Promise(function(resolve, reject) {
request.get(options, function(err, resp, body) {
if (err) {
reject(err);
} else {
resolve(JSON.parse(body));
}
})
})
}
I don't understand why I need to put return after initializePromise. Is it possible to refactor the code without return?
var initializePromise = initialize("Berlin");
return initializePromise.then(function(result) {
weather = result;
var rain = result["data"][0]["precip"];
console.log(rain);
}, function(err) {
console.log(err);
})
This all depends upon what you want to do. If you wrote this version, which is slightly altered from your original but functionally the same:
function f1(city) {
return initialize(city).then(function(result) {
const weather = result
const rain = result["data"][0]["precip"];
console.log(rain)
}, function(err) {
console.log(err)
})
}
then when you call
f1('Berlin')
you would request the Berlin result from the server, When the server responds, you would either pass to console.log the error received from the request or turn the returned body into a JS object, extract the appropriate precip property from it, and log that to the console. The resulting Promise value returned from f1 is useless, and the weather variable is unused and unusable.
If you want to log that precipitation, but still keep a useful return value, you can write:
function f2(city) {
return initialize(city).then(function(result) {
const weather = result
const rain = result["data"][0]["precip"];
console.log(rain)
return weather // *** NOTE new line here ***
}, function(err) {
console.log(err)
})
}
This time calling with Berlin (and ignoring the error case from now on), you would log the precipitation returned, but also return a Promise for the whole Berlin weather node. That means you can still do this:
f2('Berlin')
to log Berlin's first precipitation value, but that now returns a useful value, so you could do
f2('Berlin').then(console.log)
to do that same logging, and then log the entire Berlin result.
Or you could do
f2('Berlin').then(function(weather) {
// do something useful with `weather` here.
}, errorHandler)
But now note the cleanup that is available. First of all the rain variable in f2 is only used on the next line, and the weather one is simply a reference to the original result argument. So we can simplify it this way:
function f3(city) {
return initialize(city).then(function(result) {
console.log(result["data"][0]["precip"])
return result
}, function(err) {
console.log(err)
})
}
This does the same thing more simply. But now there is a much more important simplification. It's quite possible that we don't need this function at all! If we have an error handler of some sort (even if it's just console.err), and we already have a function that does most of the weather handling we want, then instead of
f3('Berlin').then(function(weather) {
// do something useful with `weather` here.
}, errorHandler)
we can add the logging line from f3 into this first callbak, and get the same result by calling directly to initialize:
initialize('Berlin').then(function(weather) {
console.log(weather["data"][0]["precip"])
// do something useful with `weather` here.
}, errorHandler)
The reason this works is because initialize returns the result of calling then on the Promise, and then f2 and f3 also return either an altered value or the original one, keeping a Promise chain intact.
I would suggest that if you're in doubt you return something in any of these situations. It makes it much easier to continue working with 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.
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