scope in call back function in setTimeout() in nodejs 8 - javascript

I am using nodejs 8 and class feature, but found this binding is unexpected.
In my below code, xxx variable should be accessible in the closure created by setTimeout, but it is not. Why?
class Wechat {
constructor(option) {
this.option = option
}
verifySource() {
console.log('verify source...')
}
async setMenu() {
console.log('setMenu...')
let thisObject = this
let nick = 'nick xu'
var xxxx = 'xxxx nick xu'
let accessToken = await this.getAccessToken()
console.log('accessToken:', accessToken)
setTimeout( function() {
// let abc = await thisObject.getAccessToken()
// no access to xxxx and nick variables
// this point to Timeout object
console.log('2nd time token:', '000000')
}, 5000 )
return Promise.resolve(33)
}
async getAccessToken() {
/*
this.access_token = 'kkkkkk'
this.expires_in = 7200
this.access_token_receive_time = 123456
*/
const timeBuffer = 60 * 10 * 1000 // 10 min
if (this.access_token && this.expires_in && this.access_token_receive_time) {
// if any of the above data exist
// check expire
let currentTime = new Date().getTime()
if (currentTime - this.access_token_receive_time > timeBuffer + this.expires_in * 1000) {
// token is valid
return this.access_token
}
}
let result = await rp.get(config.baseUrl + '/token?' +
'grant_type=client_credential&appid=' + config.appID +
'&secret=' + config.appSecret)
let resultJson = JSON.parse(result)
console.log('result of token request:', result)
this.access_token = resultJson.access_token
this.expires_in = resultJson.expires_in
this.access_token_receive_time = new Date().getTime()
return this.access_token
}
static distance() {
console.log('static method distance')
}
}
when at the debug inside the setTimeout callback.
this point to Timeout. What is going on?
check the watcher, xxxx and nick are not available...

if you just want to bind correct "this
setTimeout.call(this, function(){}) //or put whatever scope you want for this
if you wanted add argument in settimeout, you can use this
let thisObject = this
let nick = 'nick xu'
var xxxx = 'xxxx nick xu'
setTimeout (function (thisObject,nick,xxxx) {},1000,thisObject,nick,xxxx);

Related

"invalid authentication credentials" when using SetInterval with google API

I have a discord bot taking every message on a specific channel (you can type only once on it).
For each message, I put the message on a spreadsheet.
To minimize google API calls on my spreadsheet, I've implemented this :
I'm checking how many users wrote per minute.
If 10 or less : I'm calling my updatesheet function = working perfectly.
If 11 or more : I'm delaying the call with a setInterval (I took care to delay not more than 10per minute in the future aswell) = not working
I get this error message :
message: 'Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential.
which I do not understand since I'm using the exact same updatesheet function :
async function update(placeId,authorId,adressId,pointsNumber,tokensWon) {
await doc.useServiceAccountAuth({
client_email: creds.client_email,
private_key: creds.private_key,
});
await doc.loadInfo(); // loads document properties and worksheets
let sheetdailyPoints = doc.sheetsByIndex[1];
var date = new Date();
const year = date.getFullYear()
const month = `${date.getMonth() + 1}`.padStart(2, "0")
const day = `${date.getDate()}`.padStart(2, "0")
const stringDate = [day, month, year].join("/")
const rowPointsAdded = await sheetdailyPoints.addRow({
Date: stringDate,
AuthorId: authorId,
Place: placeId,
Adress: adressId,
Points: pointsNumber
});
let sheetTokensXP = doc.sheetsByIndex[3];
const rows2 = await sheetTokensXP.getRows();
var addedTokensXP = false;
let = length = rows2.length;
for(let i = 0; i < length; i++){
if(rows2[i].Adress == adressId) {
if(rows2[i].Points!='')
rows2[i].Points = parseInt(rows2[i].Points)+parseInt(pointsNumber);
else
rows2[i].Points = parseInt(pointsNumber);
rows2[i].XP = parseInt(rows2[i].XP)+200;
rows2[i].Tokens = parseInt(rows2[i].Tokens)+parseInt(tokensWon);
await rows2[i].save();
addedTokensXP=true;
return rows2[i].Tokens;
}
}
if (addedTokensXP == false) {
const rowAdded3 = await sheetTokensXP.addRow({
Adress: adressId,
Points: pointsNumber,
Tokens: tokensWon,
XP: 200
});
await rowAdded3.save();
return tokensWon;
}
}
The main function:
const prevMap = new Map();
for (i=0; i<61; i++) {
prevMap.set(i,0);
}
async function updateSheets(message,currentMin,ladderNumber,placeId,authorId,adressId,pointsNumber,tokensWon) {
if(currentMin === prevMin) {
nbUpdateAtMin = prevMap.get(currentMin);
nbUpdateAtMin++;
if(nbUpdateAtMin>10) {
let nbMinAdded = 0;
let foundPlaceAtMin;
let foundPlace = false;
for (i=currentMin+1; foundPlace; i++) {
if (prevMap.get(i) < 11 ) {
foundPlaceAtMin = i;
foundPlace = true;
}
}
nbMinAdded = foundPlaceAtMin-currentMin;
setInterval(function() {riddle.update(placeId,authorId,adressId,pointsNumber,tokensWon)},nbMinAdded*60000);
let value = prevMap.get(currentMin+nbMinAdded);
prevMap.set(currentMin+nbMinAdded,value+1);
}
else {
let value = prevMap.get(currentMin);
prevMap.set(currentMin,value+1);
riddle.update(placeId,authorId,adressId,pointsNumber,tokensWon);
}
}
else {
prevMap.set(prevMin,0);
let value = prevMap.get(currentMin);
prevMap.set(currentMin,value+1);
prevMin = currentMin;
riddle.update(placeId,authorId,adressId,pointsNumber,tokensWon)
}
}

Delay for messages in node-telegram-bot-api

I am working on a telegram bot with the node-telegram-bot-api library. I made 2 buttons using keyboard. But when you click on them a lot, the bot will spam and sooner or later it will freeze. Is it possible to somehow put a delay for the user on messages.
if (text === '/start') {
return bot.sendMessage(chatId, 'hello', keyboardMain);
}
export const keyboardMain = {
reply_markup: JSON.stringify({
keyboard: [
[{
text: '/start',
},
],
resize_keyboard: true
})
};
You can create a user throttler using Javascript Map
/*
* #param {number} waitTime Seconds to wait
*/
function throttler(waitTime) {
const users = new Map()
return (chatId) => {
const now = parseInt(Date.now()/1000)
const hitTime = users.get(chatId)
if (hitTime) {
const diff = now - hitTime
if (diff < waitTime) {
return false
}
users.set(chatId, now)
return true
}
users.set(chatId, now)
return true
}
}
How to use:
You'll get the user's chatId from telegram api. You can use that id as an identifier and stop the user for given specific time.
For instance I'm gonna stop the user for 10seconds once the user requests.
// global 10 second throttler
const throttle = throttler(10) // 10 seconds
// in your code
const allowReply = throttle(chatId) // chatId obtained from telegram
if (allowReply) {
// reply to user
} else {
// dont reply
}
I tried using this code, put the function code in my function file, connected everything to the required file, and I don’t understand what to do next and where to insert the last code and what to do with it. I'm new to JavaScript and just learning.
import {
bot
} from '../token.js';
import {
throttler
} from '../functions/functions.js';
import {
keyboardMain
} from '../keyboards/keyboardsMain.js';
export function commands() {
bot.on('message', msg => {
const text = msg.text;
const chatId = msg.chat.id;
const throttle = throttler(10);
if (text === '/start') {
const allowReply = throttle(chatId) // chatId obtained from telegram
if (allowReply) {
return bot.sendMessage(chatId, 'hello', keyboardMain);
} else {
// dont reply
}
}
return bot.sendMessage(chatId, 'error');
});
}
Get the time when he pressed the button
Get the time of the next click
Take away the difference and set the condition (if, for example, more than 3 seconds have passed between clicks, then the user will not be frozen).
var token = ""; // FILL IN YOUR OWN TOKEN
var telegramUrl = "https://api.telegram.org/bot" + token;
var webAppUrl = ""; // FILLINYOUR GOOGLEWEBAPPADDRESS
var ssId = ""; // FILL IN THE ID OF YOUR SPREADSHEET
function getMe() {
var url = telegramUrl + "/getMe";
var response = UrlFetchApp.fetch(url);
Logger.log(response.getContentText());
}
function setWebhook() {
var url = telegramUrl + "/setWebhook?url=" + webAppUrl;
var response = UrlFetchApp.fetch(url);
Logger.log(response.getContentText());
}
function sendText(id,text) {
var url = telegramUrl + "/sendMessage?chat_id=" + id + "&text=" + text;
var response = UrlFetchApp.fetch(url);
Logger.log(response.getContentText());
}
function doGet(e) {
return HtmlService.createHtmlOutput("Hi there");
}
function doPost(e){
var data = JSON.parse(e.postData.contents);
var text = data.message.text;
var id = data.message.chat.id;
var msgbegan = SpreadsheetApp.openById(ssId).getSheets()[1].getRange("A7").getValue();
var msginfo = SpreadsheetApp.openById(ssId).getSheets()[1].getRange("A9").getValue();
var answer = "%0A" + msgbegan + "%0A" ;
///////////////////////
/*
* #param {number} waitTime Seconds to wait
*/
function throttler(waitTime) {
const users = new Map()
return (chatId) => {
const now = parseInt(Date.now()/1000)
const hitTime = users.get(chatId)
if (hitTime) {
const diff = now - hitTime
if (diff < waitTime) {
return false
}
users.set(chatId, now)
return true
}
users.set(chatId, now)
return true
}
}
// global 10 second throttler
const throttle = throttler(500) // 10 seconds
// in your code
const allowReply = throttle(chatId) // chatId obtained from telegram
if (allowReply) {
// reply to user
} else {
// dont reply
}
///////////////////////////////////////
if(text == "/start"){
sendText(id, answer);
} else if (text == "/info"){
sendText(id, msginfo);
}else{
if (text.length == 10){
var found = false;
var total_rows = SpreadsheetApp.openById(ssId).getSheets()[0].getMaxRows();
for(i=1; i<=total_rows; i++){
var loop_id = SpreadsheetApp.openById(ssId).getSheets()[0].getRange(i,2).getValue();
if(text == loop_id){
found = true;
found_at = i; // employee row
break;
}
}
if(found){
sendText(id, work_message);
}else{
var msgerrror = SpreadsheetApp.openById(ssId).getSheets()[1].getRange("A6").getValue();
var not_found = "%0A" + msgerrror+ "%0A" ;
sendText(id, not_found);
}
} else {
sendText(id, "eroor");
}
}
/////////////
var emp_name = SpreadsheetApp.openById(ssId).getSheets()[0].getRange(found_at,1).getValue();
var emp_work = SpreadsheetApp.openById(ssId).getSheets()[0].getRange(found_at,3).getValue();
var homeloc = SpreadsheetApp.openById(ssId).getSheets()[0].getRange(found_at,4).getValue();
var emp_location = SpreadsheetApp.openById(ssId).getSheets()[0].getRange(found_at,8).getValue();
var emp_data = SpreadsheetApp.openById(ssId).getSheets()[0].getRange(found_at,5).getValue();
var emp_day = SpreadsheetApp.openById(ssId).getSheets()[0].getRange(found_at,6).getValue();
var emp_clock = SpreadsheetApp.openById(ssId).getSheets()[0].getRange(found_at,7).getValue();
var emp_location = SpreadsheetApp.openById(ssId).getSheets()[0].getRange(found_at,8).getValue();
var welcome = SpreadsheetApp.openById(ssId).getSheets()[1].getRange("A2").getValue();
var msgemp = SpreadsheetApp.openById(ssId).getSheets()[1].getRange("A3").getValue();
var msgloc = SpreadsheetApp.openById(ssId).getSheets()[1].getRange("A4").getValue();
var msgbay = SpreadsheetApp.openById(ssId).getSheets()[1].getRange("A5").getValue();
var msghome = SpreadsheetApp.openById(ssId).getSheets()[1].getRange("A8").getValue();
var msmobil = SpreadsheetApp.openById(ssId).getSheets()[1].getRange("A11").getValue();
var mstoday = SpreadsheetApp.openById(ssId).getSheets()[1].getRange("A13").getValue();
var msdata = SpreadsheetApp.openById(ssId).getSheets()[1].getRange("A14").getValue();
var msclock = SpreadsheetApp.openById(ssId).getSheets()[1].getRange("A15").getValue();
var work_message = welcome + emp_name +
"%0A" + msgemp + emp_work +
"%0A" + mstoday + emp_day +
"%0A" + msdata + emp_data +
"%0A" + msclock + emp_clock +
"%0A" + msghome + homeloc +
"%0A" + msgloc+ emp_location +
"%0A" + msgbay +
"%0A" + msmobil ;
}
Excuse me . I am a beginner
Is this the correct way

nodejs get video dureation

I've been trying this for ages now and I'm not making any progress.
I found this on google https://gist.github.com/Elements-/cf063254730cd754599e
and it's running but when I put that in a function and try to use it with my code its not running.
Code:
fs.readdir(`${__dirname}/data`, (err, files) => {
if (err) return console.error(`[ERROR] ${err}`);
files.forEach(file => {
if (file.endsWith(".mp4")) {
// getVideoDuration(`${__dirname}/data/${file}`)
group = new Group(file.split(".")[0], file, null, getVideoDuration(`${__dirname}/data/${file}`), 0);
groups.push(group);
}
});
console.log(groups);
});
function getVideoDuration(video) {
var buff = new Buffer.alloc(100);
fs.open(video, 'r', function (err, fd) {
fs.read(fd, buff, 0, 100, 0, function (err, bytesRead, buffer) {
var start = buffer.indexOf(new Buffer.from('mvhd')) + 17;
var timeScale = buffer.readUInt32BE(start, 4);
var duration = buffer.readUInt32BE(start + 4, 4);
var movieLength = Math.floor(duration / timeScale);
console.log('time scale: ' + timeScale);
console.log('duration: ' + duration);
console.log('movie length: ' + movieLength + ' seconds');
return movieLength;
});
});
}
Output:
[
Group {
_name: 'vid',
_video: 'vid.mp4',
_master: null,
_maxTime: undefined,
_currentTime: 0
},
Group {
_name: 'vid2',
_video: 'vid2.mp4',
_master: null,
_maxTime: undefined,
_currentTime: 0
}
]
time scale: 153600
duration: 4636416
movie length: 30 seconds
time scale: 153600
duration: 4636416
movie length: 30 seconds
its logging the information correctly but is returning undefined
This seems like a lot of extra work for little benefit, so I'm going to refer to get-video-duration https://www.npmjs.com/package/get-video-duration which does a great job of getting durations of any video file in seconds minutes and hours
Copying the last comment of the gist you sent, I came up with this:
const fs = require("fs").promises;
class Group {
constructor(name, video, master, maxTime, currentTime) {
this._name = name;
this._video = video;
this._master = master;
this._maxTime = maxTime;
this._currentTime = currentTime;
}
setMaster(master) {
if (this._master != null) {
this._master.emit('master');
}
this._master = master;
this._master.emit('master');
}
};
const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
};
async function loadGroups() {
const files = await fs.readdir(`${__dirname}/data`);
const groups = []
await asyncForEach(files, async file => {
if (file.endsWith(".mp4")) {
const duration = await getVideoDuration(`${__dirname}/data/${file}`);
const group = new Group(file.split(".")[0], file, null, duration, 0);
groups.push(group);
}
});
console.log(groups);
}
async function getVideoDuration(video) {
const buff = Buffer.alloc(100);
const header = Buffer.from("mvhd");
const file = await fs.open(video, "r");
const {
buffer
} = await file.read(buff, 0, 100, 0);
await file.close();
const start = buffer.indexOf(header) + 17;
const timeScale = buffer.readUInt32BE(start);
const duration = buffer.readUInt32BE(start + 4);
const audioLength = Math.floor((duration / timeScale) * 1000) / 1000;
return audioLength;
}
loadGroups();
As to why your original code wasn't working, my guess is that returning inside the callback to fs.open or fs.read doesn't return for getVideoDuration. I couldn't easily figure out a way from the fs docs to figure out how to return the value of the callback, so I just switched over to promises and async/await, which will essentially run the code synchronously. This way you can save the output of fs.open and fs.read and use them to return a value in the scope of getVideoDuration.
I've figured out a work-around for this problem.
async function test() {
const duration = await getDuration(`${__dirname}/data/vid.mp4`);
console.log(duration);
}
test();
function getDuration(file) {
return new Promise((resolve, reject) => {
exec(`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 ${file}`, (err, stdout, stderr) => {
if (err) return console.error(err);
resolve(stdout ? stdout : stderr);
});
});
}
I only tested it on linux so I dont know if it'll work on windows

using promises and async for protobufjs loadcall

I am trying to figure out the best way to re-write the following code:
var api = function(id, contract) {
var callback = function (error, root) {
if (error)
throw error;
var by = Buffer.from(contract,'base64')
var es = root.lookupType("Contract")
var esMsg = es.decode(by)
var esBytes = es.encode(esMsg).finish()
signature = id.sign(esBytes).toString('base64')
}
return new Promise((resolve, reject) => {
protobuf.load("contract.proto", callback)
})
}
var signContract = async(privateKey, contract) => {
let signature
var id = await crypto.keys.unmarshalPrivateKey(Buffer.from(privateKey, 'base64'))
result = await api(id,contract,signature)
}
function getSessionSignature (hash, time) {
return config.id + ":" + hash + ":" + time
}
module.exports = configure(({ ky }) => {
return async function * signbatch (input, options) {
var contracts = input.Contracts
for (var i = 0 ; i < contracts.length ; i++) {
contracts[i].contract = await signContract(config.PrivKey, contracts[i].contract)
}
//add signed contracts to the searchParams
searchParams.append("arg", JSON.stringify(contracts))
let res
res = await ky.post('storage/upload/signbatch', {
searchParams
}).json()
yield JSON.stringify({})
} else {
yield JSON.stringify({error:"Private key not found"})
}
}
})
My issue is how do I write the sign async code to pass in privateKey and contract variables to api var function and return the signature back to the result variable to be assigned to contracts[i].contract ? Please note that the id.sign(..) function is Promise inside the callback function.
You need to resolve the promise in the api function, the docs suggest you could use the single argument variant here, e.g.
var root = await protobuf.load("contract.proto");
... // (The code you currently by have in 'callback'
return signature;
As the generator is async, yield will emit a Promise which you can (obviously) handle with either .then or await

NodeJS Loop issue due to async/synchronicity issues

I am porting an old ruby script over to use javascript setting the function as a cron instance so it will run on schedule. The function queries our mysql database and retrieves inventory information for our products and then sends requests to a trading partners api to update our inventory on their site.
Due to nodes a-synchronicity I am running into issues. We need to chunk requests into 1000 items per request, and we are sending 10k products. The issue is each request is just sending the last 1000 items each time. The for loop that is inside the while loop is moving forward before it finishes crafting the json request body. I tried creating anon setTimeout functions in the while loop to try and handle it, as well as creating an object with the request function and the variables to be passed and stuffing it into an array to iterate over once the while loop completes but I am getting the same result. Not sure whats the best way to handle it so that each requests gets the correct batch of items. I also need to wait 3 minutes between each request of 1000 items to not hit the request cap.
query.on('end',()=>{
connection.release();
writeArray = itemArray.slice(0),
alteredArray = [];
var csv = json2csv({data: writeArray,fields:fields}),
timestamp = new Date(Date.now());
timestamp = timestamp.getFullYear() + '-' +(timestamp.getMonth() + 1) + '-' + timestamp.getDate()+ ' '+timestamp.getHours() +':'+timestamp.getMinutes()+':'+timestamp.getSeconds();
let fpath = './public/assets/archives/opalEdiInventory-'+timestamp+'.csv';
while(itemArray.length > 0){
alteredArray = itemArray.splice(0,999);
for(let i = 0; i < alteredArray.length; i++){
jsonObjectArray.push({
sku: alteredArray[i]['sku'],
quantity: alteredArray[i]["quantity"],
overstockquantity: alteredArray[i]["osInv"],
warehouse: warehouse,
isdiscontinued: alteredArray[i]["disc"],
backorderdate: alteredArray[i]["etd"],
backorderavailability: alteredArray[i]["boq"]
});
}
var jsonObject = {
login: user,
password: password,
items: jsonObjectArray
};
postOptions.url = endpoint;
postOptions.body = JSON.stringify(jsonObject);
funcArray.push({func:function(postOptions){request(postOptions,(err,res,body)=>{if(err){console.error(err);throw err;}console.log(body);})},vars:postOptions});
jsonObjectArray.length = 0;
}
var mili = 180000;
for(let i = 0;i < funcArray.length; i++){
setTimeout(()=>{
var d = JSON.parse(funcArray[i]['vars'].body);
console.log(d);
console.log('request '+ i);
//funcArray[i]['func'](funcArray[i]['vars']);
}, mili * i);
}
});
});
You would need async/await or Promise to handle async actions in node js.
I am not sure if you have node version which supports Async/await so i have tried a promise based solution.
query.on('end', () => {
connection.release();
writeArray = itemArray.slice(0),
alteredArray = [];
var csv = json2csv({ data: writeArray, fields: fields }),
timestamp = new Date(Date.now());
timestamp = timestamp.getFullYear() + '-' + (timestamp.getMonth() + 1) + '-' + timestamp.getDate() + ' ' + timestamp.getHours() + ':' + timestamp.getMinutes() + ':' + timestamp.getSeconds();
let fpath = './public/assets/archives/opalEdiInventory-' + timestamp + '.csv';
var calls = chunk(itemArray, 1000)
.map(function(chunk) {
var renameditemsArray = chunk.map((item) => new renamedItem(item, warehouse));
var postOptions = {};
postOptions.url = endpoint;
postOptions.body = JSON.stringify({
login: user,
password: password,
items: renameditemsArray
});
return postOptions;
});
sequenceBatch(calls, makeRequest)
.then(function() {
console.log('done');
})
.catch(function(err) {
console.log('failed', err)
});
function sequenceBatch (calls, cb) {
var sequence = Promise.resolve();
var count = 1;
calls.forEach(function (callOptions) {
count++;
sequence = sequence.then(()=> {
return new Promise(function (resolve, reject){
setTimeout(function () {
try {
cb(callOptions);
resolve(`callsequence${count} done`);
}
catch(err) {
reject(`callsequence ${count} failed`);
}
}, 180000);
});
})
});
return sequence;
}
function makeRequest(postOptions) {
request(postOptions, (err, res, body) => {
if (err) {
console.error(err);
throw err;
}
console.log(body)
});
}
function chunk(arr, len) {
var chunks = [],
i = 0,
n = arr.length;
while (i < n) {
chunks.push(arr.slice(i, i += len));
}
return chunks;
}
function renamedItem(item, warehouse) {
this.sku = item['sku']
this.quantity = item["quantity"]
this.overstockquantity = item["osInv"]
this.warehouse = warehouse
this.isdiscontinued = item["disc"]
this.backorderdate = item["etd"]
this.backorderavailability= item["boq"]
}
});
Could you please try this snippet and let me know if it works?I couldn't test it since made it up on the fly. the core logic is in the sequenceBatch function. the The answer is based on an another question which explains how timeouts and promises works together.
Turns out this wasn't a closure or async issues at all, the request object I was building was using references to objects instead of shallow copies resulting in the data all being linked to the same object ref in the ending array.

Categories

Resources