I have a section with files. Each file can be moved one position higher or lower with an arrow. Currently a request is sent to the database every time that the user moves a file, and new order updated. So if the user wants to move the bottom file to the top, clicks the 'up' arrow 10 times, 10 requests will be sent.
How can I send fewer requests than that?
My first idea was to create request with order numbers, wait 1 second, create second request and if they are the same (the user hasn't continued to change the order), then send it, if not, wait another second and compare again. But this doesn't seem to be a good thing to do. Are there any other ways?
$ctrl.saveFileOrder = function() {
var firstRequest = [];
for(var i = 0; 1 < $ctrl.fileArray.length; i++) {
firstRequest.push({$ctrl.fileArray[i].id, $ctrl.fileArray[i].orderNumber});
}
var secondRequest = [];
while(firstRequest != secondRequest) {
//put thread to sleep for 1 second here
for(var i = 0; 1 < $ctrl.fileArray.length; i++) {
secondRequest.push({$ctrl.fileArray.id[i], $ctrl.fileArray.orderNumber[i]});
}
}
//send the final request
}
You could disable the button until a response has been received from the server so that the user cannot click again before the DB has been updated.
const buttons = document.querySelectorAll('.js-move');
/**
* Simulation of a request with a 2 second duration
*/
const sendRequest = value => new Promise(resolve => {
console.log(`Updating to DB: ${value}`);
buttons.forEach(button => button.disabled = true);
setTimeout(() => {
console.log('Update done');
buttons.forEach(button => button.disabled = false);
resolve();
}, 2000);
});
buttons.forEach(button => {
button.addEventListener('click', event => {
const { value } = event.target;
sendRequest(value);
});
});
<button class="js-move" value="-1">Move up</button>
<button class="js-move" value="+1">Move down</button>
Alternatively you are looking for a throttle function which, like the name suggest, bottlenecks the amount of calls by a specified amount set by you. So only once every second could you make a new request.
const throttle = (callback, wait, immediate = false) => {
let timeout = null;
let initialCall = true;
return (...args) => {
const callNow = immediate && initialCall
const next = () => {
callback(...args);
timeout = null;
}
if (callNow) {
initialCall = false;
next();
}
if (!timeout) {
timeout = setTimeout(next, wait);
}
}
}
const buttons = document.querySelectorAll('.js-move');
/**
* Mock function where you send a request.
*/
const sendRequest = value => {
console.log(`Updating to DB: ${value}`);
}
/**
* Create throttled version of your request.
* The number indicates the interval in milliseconds between
* calling the sendRequest function.
*/
const throttledRequest = throttle(sendRequest, 1000, true);
buttons.forEach(button => {
button.addEventListener('click', event => {
const { value } = event.target;
throttledRequest(value);
});
});
<button class="js-move" value="-1">Move up</button>
<button class="js-move" value="+1">Move down</button>
Related
I am trying to figure out how I can update the playerValue for my Rock Scissor Paper game. I have a function that registers a button click and should then update the playerValue accordingly(1,2 or three depending on which button was clicked). The problem is, when I call the function, the playerValue remains 0 and I don't know what I need to change to fix that. playerValue itself is defined at the very beginning of my file. It starts out as 0.
Here is my JavaScript code(or at least the relevant part of it):
//register button click and:
function player_choose_value(){
//check which button has been clicked -> rock 1, scissor 2 or paper 3
btnRock.addEventListener("click", () =>{
playerValue = 1;
});
btnScissor.addEventListener("click", () =>{
playerValue = 2;
});
btnPaper.addEventListener("click", () =>{
playerValue = 3;
});
}
This here is where the playerValue is meant to be used. The playerValue is always 0. I think that is because the player_choose_value() function does not wait for the click event to happen. So the function is executed but the user does not have the chance to actually click a button, so it stays 0:
function play_round(){
let computerValue = computer_choose_value();
player_choose_value();//is always zero
console.log(playerValue);
won_tie_lost(computerValue, playerValue);
}
I was wondering how I could add a "wait for one of the three buttons to be clicked" functionality?
In your case, player_choose_value doesn't wait until the player has actually picked a value.
You could probably do this using async await/promises:
function player_choose_value(){
return new Promise(resolve => {
bntRock.onclick = () => resolve(1)
btnScissor.onclick = () => resolve(2)
btnPaper.onclick = () => resolve(3)
})
}
async function play_round(){
let computerValue = computer_choose_value();
const playerValue = await player_choose_value();
console.log(playerValue);
won_tie_lost(computerValue, playerValue);
}
;(async function main() {
while (true) { // Keep repeating the game
await play_round()
}
})()
Use promise.
function player_choose_value() {
return new Promise((resolve, reject) => {
//check which button has been clicked -> rock 1, scissor 2 or paper 3
document.querySelector('#btn1').addEventListener("click", () => {
resolve(1);
});
document.querySelector('#btn2').addEventListener("click", () => {
resolve(2);
});
document.querySelector('#btn3').addEventListener("click", () => {
resolve(3);
});
});
}
player_choose_value().then(
play_value => {
alert(play_value);
// won_tie_lost...
// other code 2...
}
);
// other code 1...
<button id='btn1'>btn1</button>
<button id='btn2'>btn2</button>
<button id='btn3'>btn3</button>
player_choose_value will called first. It returns a promise that is pending.
The engine will continue to execute other code 1 and the code in then(play_value => { block will not be executed until one of the resolves is called (the promise is fulfilled).
Here is another pattern. I think it suits your needs better.
document.querySelectorAll('button').forEach((button, index) => {
button.addEventListener('click', () => {
let playerValue = index;
let computerValue = computer_choose_value();
play_round(playerValue, computerValue);
})
});
function play_round(playerValue, computerValue) {
// Disable the buttons if needed
alert(`${playerValue} ${computerValue}`);
// won_tie_lost(computerValue, playerValue);
}
function computer_choose_value() {
return ~~(Math.random() * 3);
}
// start a round by enable the buttons
<button id='btn1'>btn1</button>
<button id='btn2'>btn2</button>
<button id='btn3'>btn3</button>
You're going at it in a totally wrong way.
You should have play_round() be called by the button clicks, otherwise you will add event listeners on the button every single round.
btnRock.addEventListener("click", play_round);
btnScissor.addEventListener("click", play_round);
btnPaper.addEventListener("click", play_round);
function play_round(event){
let playerValue = event.target.dataset.value;
let computerValue = computer_choose_value();
won_tie_lost(computerValue, playerValue);
}
function computer_choose_value() {
return Math.floor(Math.random() * 3) + 1;
}
function won_tie_lost(playerValue, computerValue) {
console.log(`player: ${playerValue}, computer: ${computerValue}`);
}
<button id="btnRock" data-value="1">Rock</button>
<button id="btnScissor" data-value="2">Scissor</button>
<button id="btnPaper" data-value="3">Paper</button>
I have a piece of code where I consume events from RabbitMQ and save the events(3 types of events A,B,C) into 2 different databases A,B. I can push into Database A without any problem but I need to wait for no of events to be at least 100 to push events of type B & C into database or until the code is trying to fill up the queue for last 5 minutes from the point saveToDb in invoked. I am not able to figure out how to wait for events for B and C and then save data in database.
Note that Event A will go into Database A and Event B,C will go into Database B.
I have written following piece of code.
import { Channel, ConsumeMessage } from 'amqplib';
const BATCH_SIZE = 100;
var eventBQueue = [];
var eventAQueue = [];
const shiftElements = (message) => {
if ( message.length >= BATCH_SIZE) {
const batch= message.splice(0, BATCH_SIZE);
return batch;
}
return message;
}
const saveToDb = async (messages, database) => {
const eventsA = filterEventsA(messages);
const eventsB = filterEventsB(messages);
const eventsC = filterEventsC(eventsB);
const promises = [];
promises.push(databaseAsync.publish(eventsC));
if (eventBQueue.length < BATCH_SIZE) {
eventBQueue.push.apply(eventBQueue, eventsB);
}
else {
var eventsBBatched = shiftElements(eventBQueue);
promises.push(database.publish(eventsBBatched, EVENTS_TABLE_A));
}
if (eventAQueue.length < BATCH_SIZE) {
eventAQueue.push.apply(eventAQueue, eventsA);
}
else {
var eventsABatched = shiftElements(eventAQueue);
promises.push(database.publish(eventsABatched, EVENTS_TABLE_B));
}
return new Promise((resolve, reject) => {
Promise.all(promises).then(resolve).catch(reject);
});
}
export const process = async (database,
rabbitmq): Promise<void> => {
return new Promise((resolve, _) => {
rabbitmq.consume(async (channel, message: ConsumeMessage) => {
const messages = somefunction(message);
await saveToDb(messages,database)
.then(_ => {
try {
channel.ack(message)
} catch (error) {
}
})
.catch((error) => {
try {
console.error('error');
channel.ack(message)
} catch (error) {
}
});
});
somefunction(resolve)
});
}
Now I want to add some condition in if where no of events < Batch_SIZE to wait for data from rabbitMQ and to save in database when eventAQueue and eventBQueue has adequate size or there is a time limit waiting for this data. But I am not sure how to add it.
In one of my bot's dialog steps I'am lanching some operations in a setTimeout() function.
The goal is to clear that TimeOut in an other step in some conditions.
async saveAdults(step) {
if (step.result) {
step.values.adults = step.result;
const convId = step.context.activity.conversation.id;
const format = "dddd DD MMMM YYYY";
// Send partial notification in case of a delay of 5 minutes
const data = {
checkIn: step.values.checkIn,
nights: step.values.nights,
adults: "",
children: ""
};
const timer = await sendPartialNotification(convId, data);
// step.values.timer = timer;
this.notificationProp.set(step.context, timer);
await this.conversationState.saveChanges(step.context);
}
return await step.next();
}
exports.sendPartialNotification = async (convId, data) => {
const interval = 300000;
const timer = setTimeout(() => {
notify(convId, this.id, data, true);
}, interval);
return timer;
};
async notifyClient(step) {
const timer = this.notificationProp.get(step.context);
clearTimeout(timer);
// …
}
Trying to store the TimeOut object in step.values.timer or in the conversation state throws this error that indicates that it is not possible to parse the Timeout Object ...
TypeError: Converting circular structure to JSON
As solution to this, I was thinking about storing the timer in Redis ..
Is there any ideas? Thanks.
Use state, props, or equivalent to pass the value from one step to the next. In my example code below, I include a middle step asking if the client would like to cancel. This is purely for displaying output for the solution.
Initiate the timer in a lead step.
async setTimer(step) {
if (step.result) {
const convId = step.context.activity.conversation.id;
const data = {
value1: someValue1,
value2: someValue2
};
const timer = await sendPartialNotification(convId, data);
this.notificationProp = { step: step.context, timer: timer };
await this.conversationState.saveChanges(step.context);
}
return await step.next();
}
Ask the client, in an intermediary step, if they would like to cancel the timer. I have the timer set for 10 secs.
If the user cancels, the timer is cleared.
If the client declines or fails to respond before 10 secs is up, the timer is unaffected and executes.
async askClient(step) {
const timer = this.notificationProp.timer;
if (timer._idleTimeout > 0) {
const message = MessageFactory.text(
'Cancel the timer?',
null,
'expectingInput'
);
return await step.prompt('confirmPrompt', message);
}
}
Lastly, output results and notify the client.
async notifyClient(step) {
const stepResult = step.result;
step.value = { timer: this.notificationProp.timer };
if (stepResult === true) {
console.log('TIMER PRE-CLEAR ', step.value.timer);
const timer = step.value.timer;
await clearTimeout(timer);
console.log('TIMER POST-CLEAR', timer);
step.context.sendActivity('Cancelling timer');
} else {
step.context.sendActivity('Timer not cancelled');
}
return await step.next();
}
Timer not cancelled and executes:
Timer cancelled:
Hope of help!
If I get enough users on my application, sending an ajax request with each keystroke is an effective way to bring the server to its knees (not to mention possibly making the client application feel quite sluggish). On implementing a symbol search box with two options (DB Search and Web Api Search). While I am typing the symbol (ex: AAPL - aple stock) in the search box the fetch() request is sent each time over the network. To avoid it I tried to use setTimeout() but fetch() request is sent multiple times, anyway. How to delay/start/debounce fetching the request until user stops typing in input area to send only one fetch() request?
HTML:
<label for="symbolTags">Symbol: </label>
<input type="text" id="symbolTags" name="symbol">
<label for="api">Select Search Api: </label>
<select id="api" name="routes_api">
<option value="search">Web Search Api</option>
<option value="dbsearch">DB Search Api</option>
</select>
JavaScript:
const symbolTags = document.querySelector('#symbolTags')
const symbolTagsOptions = document.querySelector('#api')
const urlsObject = {
dbsearch: '/dbsearch/',
search: '/search/'
}
symbolTags.oninput = function () {
let symbolTagsOptionsValue = symbolTagsOptions.value
let arg = urlsObject[symbolTagsOptionsValue]
// Init a timeout variable to be used below
let timeout = null
// Clear the timeout if it has already been set.
// This will prevent the previous task from executing
// if it has been less than <MILLISECONDS>
clearTimeout(timeout)
// Make a new timeout set to go off in 2000ms
timeout = setTimeout(function () {
requestSymbolSearch(arg)
}, 2000)
}
function requestSymbolSearch(arg) {
getData(arg)
.then(data => {
console.log(data)
$('#symbolTags').autocomplete({
source: data.map(item => item.symbol),
autoFocus: true
})
})
.catch(error => console.error('Error:', error))
}
function getData(url) {
let curValueSymbol = symbolTags.value
let urlPlus = `${url}${curValueSymbol}`
console.log(urlPlus)
return fetchData(urlPlus)
}
async function fetchData(urlPlus) {
const dataResponse = await fetch(urlPlus)
const dataJson = await dataResponse.json()
return dataJson
}
Here is the console result:
Here is the network result:
This is commonly solved by debouncing the event; which collapses multiple calls in a given timeframe to just one:
// Debounce function from: https://stackoverflow.com/q/24004791/1814486
const debounce = (func, wait, immediate) => {
let timeout
return function() {
const context = this, args = arguments
const later = function() {
timeout = null
if (!immediate) func.apply(context, args)
}
const callNow = immediate && !timeout
clearTimeout(timeout)
timeout = setTimeout(later, wait)
if (callNow) func.apply(context, args)
}
}
// If there's not another `input` within 500ms log the value,
// otherwise ignore the event.
document.querySelector('#input').addEventListener('input', debounce(() => {
console.log(input.value)
}, 500))
<input id="input" placeholder="Type fast here.."/>
I could use a bit of help. I'm trying to an experiment where I start my ajax request on mouseenters of my button, but only consume the result of that request if the user clicks (or if the request hasn't finished yet when the user clicks, consume the result as soon as it does). If the user leaves the button without clicking, the request should be cancelled.
Where I'm stuck is how to merge the click stream with the request stream. I cannot use withLatestFrom, because then if the request finishes after the click, it will not be consumed. I also cannot use combineLatest, because then if any click has occurred in the past, the the request will be consumed, even if I'm currently just mousing over.
Would love some guidance. It's been a fun problem to think about but I'm stuck
const fetchContent = (url) => {
const timeDelay$ = Rx.Observable.timer(1000); // simulating a slow request
const request$ = Rx.Observable.create(observer =>
fetch(url, { mode: 'no-cors' })
.then(json => {
observer.onNext('res')
observer.onCompleted()
})
.catch(e => observer.onError())
)
return timeDelay$.concat(request$)
}
const hover$ = Rx.Observable.fromEvent(myButton, 'mouseenter')
const leave$ = Rx.Observable.fromEvent(myButton, 'mouseleave')
const click$ = Rx.Observable.fromEvent(myButton, 'click')
const hoverRequest$ = hover$
.flatMap(e =>
fetchContent(e.target.getAttribute('href'))
.takeUntil(leave$.takeUntil(click$))
)
const displayData$ = click$
.combineLatest(hoverRequest$)
displayData$.subscribe(x => console.log(x))
You aren't terribly far off actually. You are just missing the inclusion of zip really. Since what you really need for propagation is for both a mouse click and the request to complete. By zipping the request and the mouse click event you can make sure that neither emits without the other.
const hover$ = Rx.Observable.fromEvent(myButton, 'mouseenter');
const leave$ = Rx.Observable.fromEvent(myButton, 'mouseleave');
const click$ = Rx.Observable.fromEvent(myButton, 'click');
//Make sure only the latest hover is emitting requests
hover$.flatMapLatest(() => {
//The content request
const pending$ = fetchContent();
//Only cancel on leave if no click has been made
const canceler$ = leave$.takeUntil(click$);
//Combine the request result and click event so they wait for each other
return Rx.Observable.zip(pending$, click$, (res, _) => res)
//Only need the first emission
.take(1)
//Cancel early if the user leaves the button
.takeUntil(canceler$);
});
Maybe you could conceptualize this as three events (hover, leave, click as you call them) triggering three actions (emit request, cancel request, pass request result) modifying a state (request? pass request?).
Now this is done in a rush on a sunday evening, but with a little bit of luck , something like this could work :
function label(str) {return function(x){var obj = {}; obj[str] = x; return obj;}}
function getLabel(obj) {return Object.keys(obj)[0];}
const hover$ = Rx.Observable.fromEvent(myButton, 'mouseenter').map(label('hover'));
const leave$ = Rx.Observable.fromEvent(myButton, 'mouseleave').map(label('leave'));
const click$ = Rx.Observable.fromEvent(myButton, 'click').map(label('click'));
var initialState = {request : undefined, response : undefined, passResponse : false};
var displayData$ = Rx.Observable.merge(hover$, leave$, click$)
.scan(function (state, intent){
switch (getLabel(intent)) {
case 'hover' :
if (!state.request) {
state.request = someRequest;
state.response$ = Rx.Observable.fromPromise(executeRequest(someRequest));
}
break;
case 'leave' :
if (state.request && !state.passResponse) cancelRequest(someRequest);
state.passResponse = false;
break;
case 'click' :
if (!state.request) {
state.response$ = Rx.Observable.fromPromise(executeRequest(someRequest));
}
state.passResponse = true;
}
}, initial_state)
.filter(function (state){return state.passResponse;})
.pluck('response$')
.concatAll()