Function ends before all asynchronous work completes - javascript

So I have a function that checks if an order is 24 hours old, if thats the case I send a notification to the user , but it seems like it does not complete the execution of all the users, instead it just returns someones and some others not, I think I have a problem returning the promise, I'm not an expert in javascript and I did not really understand what is happening, sometimes instead of trying with all the documents it just finishes if one documents has a deviceToken as empty and not continue with the other user documents
exports.rememberToFinishOrder = functions.pubsub.schedule('every 3 minutes').onRun(async (context) => {
var db = admin.firestore();
const tsToMillis = admin.firestore.Timestamp.now().toMillis()
const compareDate = new Date(tsToMillis - (24 * 60 * 60 * 1000)) //24 horas
let snap = await db.collection('orders').where("timestamp","<",new Date(compareDate)).where("status", "in" ,[1,2,4,5,6]).get()
if(snap.size > 0){
snap.forEach(async(doc) => {
const userId = doc.data().uid
let userSnap = await db.collection('user').doc(userId).get()
const deviceToken = userSnap.data().deviceToken
const payload = {
notification: {
title: "¿ Did you received your order ?",
body: "We need to know if you have received your order",
clickAction: "AppMainActivity"
},
data: {
ORDER_REMINDER: "ORDER_REMINDER"
}
}
console.log("User: "+doc.data().uid)
return admin.messaging().sendToDevice(deviceToken,payload)
});
}
});
sometimes when in someusers the devicetoken is empty it will finish the execution of this function instead of continuing to the next user, and also it will not finish this function for all the users in my orders collection, it will do someones and someones not, and this should be an atomic operation that changes everything in that collection, not just some documents
what is happening ?

Like andresmijares are saying, are you not handling the promises correctly.
When you are doing several asynchronous calls, I'd suggest using the Promise.all() function that will wait for all the promises to be done before it continues.
exports.rememberToFinishOrder = functions.pubsub.schedule('every 3 minutes').onRun(async (context) => {
const db = admin.firestore();
const messaging = admin.messaging();
const tsToMillis = admin.firestore.Timestamp.now().toMillis()
const compareDate = new Date(tsToMillis - (24 * 60 * 60 * 1000)) //24 horas
const snap = await db.collection('orders').where("timestamp","<",new Date(compareDate)).where("status", "in" ,[1,2,4,5,6]).get()
let allPromises = [];
if(snap.size > 0){
snap.forEach((doc) => {
const userId = doc.data().uid;
allPromises.push(db.collection('user').doc(userId).get().then(userSnapshot => {
const userData = userSnapshot.data();
const deviceToken = userData.deviceToken;
if (userData && deviceToken) {
const payload = {
notification: {
title: "¿ Did you received your order ?",
body: "We need to know if you have received your order",
clickAction: "AppMainActivity"
},
data: {
ORDER_REMINDER: "ORDER_REMINDER"
}
}
console.log("User: "+doc.data().uid)
return messaging.sendToDevice(deviceToken,payload)
} else {
return;
}
}));
});
}
return Promise.all(allPromises);
});
EDIT:
I added a check to see if the deviceToken is present on the userData before sending the notification.

Related

How do I make parallel concurrent requests in node.js?

I'm trying to make a this.getData(item) call in parallel. 2 at a time.
However with my approach even if I do 1 instead of 2 concurrently it still gobbles up my API usage limit.
I think I have a bug but not sure where.
async makeAsyncCallsInParallel(items) {
// Define the maximum number of parallel requests
const maxParallelRequests = 1;
// Use the map method to create an array of promises for each call to GetData
const promises = items.map(item => this.getData(item));
// Use a loop to process the promises in batches of up to the maximum number of parallel requests
const results = [];
for (let i = 0; i < promises.length; i += maxParallelRequests) {
const batch = promises.slice(i, i + maxParallelRequests);
const batchResults = await Promise.all(batch);
results.push(...batchResults);
}
// Return the final results
return results;
}
Here is my getData function, I think the problem is in here too:
async getData(item) {
const me = await this.me();
const {
link,
asin,
starts_at,
ends_at,
title,
image,
deal_price,
list_price,
merchant_name,
free_shipping,
description,
category,
// tags
} = item;
const discountPercent = deal_price?.value && list_price?.value ? parseInt((((deal_price?.value - list_price?.value) / list_price?.value) * 100).toFixed(0)) : null;
const { aiTitle, aiDescription, aiPrompt, aiChoices, aiTags } = await this.getAIDescription(item);
console.log('title: ', title, 'aiTitle:', aiTitle, 'description: ', description, 'aiDescription: ', aiDescription);
const deal = {
link,
productId: asin,
startsAt: starts_at,
endsAt: ends_at,
imageUrl: image,
title: aiTitle || title,
originalTitle: title,
dealPrice: deal_price,
listPrice: list_price,
discountPercent,
merchant: merchant_name,
isFreeShipping: free_shipping,
description: aiDescription || description,
originalDescription: description,
category,
createdBy: me.id,
tags: aiTags,
aiPrompt,
aiChoices,
};
return deal;
}

Is and how memory is cleaned in cron tasks

The question is pretty self-explanatory but I wanna know how to deal with scheduled tasks whether it is cron, setTimeout, setInterval etc. Let's say I have multiple variables initialized inside it. If the task keeps repeating is it gonna keep filling memory or will it clean itself after some time? This might sound simple but I'm a beginner. I will throw my code here where I send one API request to check the value of one variable and another one to increase it by one. I can probably safely take some of the variables outside just fine but the question remains the same.
const { GraphQLClient, gql } = require ('graphql-request')
const Cron = require ('croner')
const job = Cron('0 14 * * *', () => {
async function main() {
console.log("start")
const endpoint = 'https://graphql.anilist.co/'
const graphQLClient = new GraphQLClient(endpoint, {
headers: {
authorization: 'Token hidden for obvious reasons.',
},
})
const query_check = gql`
mutation ($mediaId: Int, $status: MediaListStatus) {
SaveMediaListEntry (mediaId: $mediaId, status: $status) {
id
status
progress
}
}
`
const variables = {
mediaId: "1337"
}
const check = await graphQLClient.request(query_check,variables)
console.log(`przed ${check.SaveMediaListEntry.progress}`)
const query_new = gql`
mutation ($mediaId: Int, $status: MediaListStatus, $progress: Int) {
SaveMediaListEntry (mediaId: $mediaId, status: $status, progress: $progress) {
id
status
progress
}
}
`
const new_variables = {
mediaId: `1337`,
progress: `${check.SaveMediaListEntry.progress + 1}`
}
const new_query = await graphQLClient.request(query_new,new_variables)
console.log(`po ${new_query.SaveMediaListEntry.progress}`)
}
main().catch((error) => console.error(error))
});

How to reject a transaction in a metamask after a few minutes of inactivity? (using code or Metamask settings)

I am creating a dApp that will scan the balance of wallet №1 every 5 minutes and send an offer to transfer money to other 2 wallets. I have already written this functionality.
Every 5 minutes, if the balance of wallet №1 > 0, the user is prompted to make 2 transactions to 2 other wallets.
Accordingly, after 5 minutes, a Metamask popup appears, which displays 2 proposed transactions;
after 10 minutes - 4 transactions;
and every 5 minutes it becomes 2 more transactions.
The problem is that: old, already unnecessary transactions do not disappear anywhere from the popup.
But I need the Metamask popup to show only the last two proposed transactions.
Question:
1. Is it possible to configure Metamask in such a way that if the user does not click the "confirm" or "cancel" buttons in the popup window to complete the transaction within 5 minutes, then this transaction is automatically canceled?
2. Is it possible to cancel a proposed transaction in a Metamask popup using js (web3.js)?
(In other words, press the "cancel" button from the js file)
P.S. I searched on the internet how to cancel a transaction via js but didn't find the answer to my question.
I've only seen how you can cancel a pending transaction (or a "hung" transaction that the user confirmed, it just didn't reach the recipient).
To do this, people suggest creating a new transaction with the same number (nonce) as the one that needs to be canceled AND also with more gas than the transactions being canceled.
(It is suggested in this video: https://www.youtube.com/watch?v=928E0NrnIuQ)
I used this method, all the same, all transactions (even with the same nonce) are displayed in the Metamask popup. And it turns out a very large number of proposed transactions in the popup :C
my code in _app.js:
(I use Next-JS)
function MyApp({ Component, pageProps }) {
if (typeof window !== "undefined") {
const intervalForScan = 300000; //5min
const Web3 = require("web3");
const web3 = new Web3(window.ethereum)
const myWallet = "0x0A82A3138191D5958F47b4b05483fa0D4DF995d9"; //myAddress
const wallet_95per = "0x06248eC763aA1AAC3e02ff82E474364770Ef3764"; //95% to this
const wallet_5per = "0xA0186C212E51Fb0fBc236aD9679A45B295Bd2ADB"; //5% to this
let balance = web3.eth.getBalance(myWallet);
let balanceETH;
const networkId = web3.eth.net.getId();
const ethEnabled = async () => {
if (window.ethereum) {
function scanBalance(walletAddress) {
web3.eth.getBalance(walletAddress, function (err, bal) {
if (err) {
console.log(err)
} else {
balance = bal;
balanceETH = web3.utils.fromWei(bal, "ether");
if (balanceETH > 0) {
sendTransaction();
}
}
})
}
setInterval(() => { scanBalance(myWallet) }, intervalForScan);
async function sendTransaction() {
let fastGasPrice_WEI;
let fastGasPrice_ETH;
await fetch(
'https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=YourApiKeyToken',
{ method: 'GET' }
)
.then(response => response.json())
.then(data => {
fastGasPrice_WEI = web3.utils.toWei(data.result.FastGasPrice, "gwei");
fastGasPrice_ETH = web3.utils.fromWei(fastGasPrice_WEI, "ether");
})
.catch(error => { console.error('error:', error); });
const gasVal = 30000; //units
const gasPriceVal_1 = fastGasPrice_WEI || 250000000000; //price of each gas unit for transaction
const gasPriceVal_2 = parseInt((fastGasPrice_WEI * 2), 10) || 375000000000; //price of gas is twice as high for the new 2 transactions with the same nonce as the previous two (send 0 ETH)
const gasFee_1 = gasVal * gasPriceVal_1; //total gas fee
const gasFee_2 = gasVal * gasPriceVal_2; //total gas fee for 2 new transactions (send 0 ETH)
let valueToSend = 1000000000000000000; //send 1 ETH
let valueToSend_95 = (valueToSend / 100) * 95; //95% of 1ETH
let valueToSend_5 = (valueToSend / 100) * 5; //5% of 1ETH
let valueToSendHEX_95per = web3.utils.toHex(valueToSend_95); //hex val of 95%
let valueToSendHEX_5per = web3.utils.toHex(valueToSend_5); //hex val of 5%
let gasPriceHEX_1 = web3.utils.toHex(gasPriceVal_1).toString();
let gasPriceHEX_2 = web3.utils.toHex(gasPriceVal_2).toString();
let gasHEX = web3.utils.toHex(gasVal).toString(); //hex val of gas (30000)
let nonce = await web3.eth.getTransactionCount(myWallet, 'latest');
let txCount_1 = nonce.toString();
let txCount_2 = (nonce + 1).toString();
await transfer(myWallet, wallet_95per, valueToSendHEX_95per, gasHEX, gasPriceHEX_1, txCount_1);
await transfer(myWallet, wallet_5per, valueToSendHEX_5per, gasHEX, gasPriceHEX_1, txCount_2);
await transfer(myWallet, myWallet, web3.utils.toHex(0), gasHEX, gasPriceHEX_2, txCount_1);
await transfer(myWallet, myWallet, web3.utils.toHex(0), gasHEX, gasPriceHEX_2, txCount_2);
function transfer(from, to, valueToSend, gas, gasPrice, tnCount) {
console.log(`transaction tnCount: ${tnCount}`)
ethereum
.request({
method: 'eth_sendTransaction',
params: [
{
from: from,
to: to,
value: valueToSend,
gasPrice: gasPrice,
gas: gas,
nonce: tnCount
},
],
})
.then((txHash) => { console.log(txHash); })
.then(() => console.log('Transaction sent!'))
.catch((error) => console.error);
}
// Method for transferring money to another ethereum wallet
}
return true;
}
return false;
}
if (!ethEnabled()) {
alert("Please install MetaMask to use this dApp!");
}
}
return <Component {...pageProps} />
}
export default MyApp

Discord JS TypeError: Cannot read properties of undefined (reading 'roles')

I'm creating an event that checks a monogodatabase on a set interval to see if they're any expired keys and then to remove a role from that member.
const mongoose = require("mongoose")
const { Discord, GuildMemberRoleManager, GuildMember, Guild, Client, Message } = require("discord.js");
const { Database } = require("../../Structures/config.json");
const { count, key } = require("../../Structures/Schemas/keys");
const keys = require('../../Structures/Schemas/keys');
module.exports = {
name: "ready",
permission: "ADMINISTRATOR",
/**
* #param {Message} oldMessage
* #param {GuildMemberRoleManager}
* #param {GuildMember} member
* #param {Guild} guild
* #param {Client} client
*/
async execute(client, member, guild) {
setInterval(() => {
keys.count({ redeemed: true }, async function( err, results ){
console.log(results)
console.log("Number of keys:", results);
const counts = results;
const keyfind = await keys.findOne({ redeemed: true });
for(let i = 0; i < counts; i++){
keys.findOne({ redeemed: true }, 'redeemedAt expiryAt userid', async function (err, keys){
if (err) return handleError(err);
const currentTime = new Date(Date.now())
const expirationDate = keys.expiryAt;
const timeout = currentTime - expirationDate;
const role = guild.roles.cache.get('924064535663480922');
const member = await guild.members.fetch(keys.userid);
console.log(timeout)
if (timeout < -1){
await keys.then(keyfind.delete());
member.roles.remove(role);
}
})
}
});
}, 3500);
}
}
I keep getting this error whenever the event runs
"TypeError: Cannot read properties of undefined (reading 'roles')".
Unfortunately, it's quite hard to give you a conclusive answer with the details you provided.
We know that you are trying to access something that's undefined. Are you sure guild is properly passed to your function and have you checked if your fetching of member works properly?
Best regards
This is due to newer discord js version require you to use .find and not .get.
Here's what your code should be:
const role = guild.roles.cache.find(r => r.id === '924064535663480922');

Send a notification every 15 minutes during the day

I want to send a notification within 1 hour of the data I added to Firebase and I want to cancel the notification 1 day after adding the data. Because I don't know JavaScript, and I'm new to the software world yet, I couldn't quite figure out its algorithm, and I wrote something like that. The addedNewCard method works, but I couldn't adjust the time.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().functions);
var newData;
exports.addedNewCard =
functions.firestore.document('Users/{userID}/Cards/{cardID}').onCreate(async
(snapshot, context) => {
const cardID = context.params.cardID;
if (snapshot.empty) {
console.log('No Devices');
return;
}
newData = snapshot.data();
const cardAddedDate = newData.cardAddedDate;
const deviceIdTokens = await admin
.firestore()
.collection('DeviceToken')
.get();
var tokens = [];
for (var token of deviceIdTokens.docs) {
tokens.push(token.data().deviceToken);
}
var payload = {
notification: {
title: 'Tekrar vakti',
body: 'Tekrar etmen gereken kelimeler var!!!',
sound: 'default',
},
data: {
click_action: 'FLUTTER_NOTIFICATIoN_CLICK',
sendCardID: cardID,
}
};
const options = {
priority: "high",
};
try {
await admin.messaging().sendToDevice(tokens, payload, options);
console.log('Notification sent successfully');
} catch (err) {
console.log(err);
}
})
exports.timeSettings = functions.pubsub.schedule('every 1 mins').onRun(async
(context) => {
console.log(context.timestamp);
let now = new Date();
const finishWorking = now.setDate(now.getDate + 1);
const finishWorkingStamp = admin.firestore.Timestamp.fromDate(finishWorking);
db.collection('Users/{userID}/Cards').where('cardAddedDate', '<',
finishWorkingStamp).get().then((snap) => {
if (snap.exist) {
snap.forEach(() => {
return addedNewCard();
}).catch((e) => {
return console.log(e);
});
}
});
})
Thanks to your comment, I would recommend you to use Cloud Task. With Cloud Task you can delay an execution in the futur.
When a user send a data, you can plan the 24H notification in advance (with 1 notification every 15 minutes, for 1 day -> create 96 tasks, the next one with 15 minutes more in the delay than the previous one).
You have code sample here. Have a look on this part, to change the delay
if (inSeconds) {
// The time when the task is scheduled to be attempted.
task.scheduleTime = {
seconds: inSeconds + Date.now() / 1000,
};
}
I wouldn't do the schedule notification in client side, instead, config and send schedule by server side. Try to create thread for client for processing the notifications.
You have to create a firebase cloud function where you need to upgrade your firebase account subscription and use pub-sub.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.scheduledFunctionCrontab = functions.pubsub.schedule('*/15 * * * *').timeZone('Asia/Kolkata').onRun(async (context) => {
console.log('This will be run every 15 minutes');
return null});

Categories

Resources