Javascript - Local object variable not conserved between promise and await - javascript

I am using the code below which higlights the fact that local object variable (HitCurrent) is not conserved between the core of computeHit(HitCurrent,'computer') and after the calling of this function in the following way : await computeHit(HitCurrent, 'computer');
In the core of computeHit(HitCurrent,'computer'), array HitCurrent.arrayCurrent is modified (actually, it computes the hit for computer) : But the issue is that modifications are not conserved once I come back to the main thread (after await computeHit(HitCurrent, 'computer').
If I do, after the await, a console.log of an array contained in this local object variable, I don't get the same array than one which is right computed into core of computeHit function. I don't understand this behavior.
// Perform hit computer
(async () => {
// Wait computeHit function
await computeHit(HitCurrent, 'computer');
// Output array which is not the same than into computeHit
console.log('into async : HitCurrent.arrayCurrent', HitCurrent.arrayCurrent);
alert('into async() function');
})();
}
with computerHit function like :
function computeHit(HitCurrent, mode) {
if (mode == 'computer') {
return new Promise( resolve => {
// Creation of webworker
let firstWorker = new Worker(workerScript);
firstWorker.onmessage = function (event) {
resolve(event.data);
}
// Post current copy of HitCurrent, i.e HitCurrent
firstWorker.postMessage([HitCurrent, HitCurrent.playerCurrent, maxNodes]);
}).then(({result}) => {
// Get back game board of webworker
HitCurrent = result.HitResult;
// Get back suggested hit computed by webworker
[a,b] = HitCurrent.coordPlayable;
// Drawing all lines from suggested hit (in 8 directions)
// HitCurrent.arrayCurrent is modified HERE !
for (k = 0; k < 8; k++) {
exploreHitLine(HitCurrent, a, b, k, 'drawing');
}
// Remove playable hits
cleanHits('playable', HitCurrent);
// Display current game
displayCurrentHit(HitCurrent);
// Output array which is good
alert('into computeHit function');
console.log('into promise - HitCurrent.arrayCurrent', HitCurrent.arrayCurrent);
})
}
}
I expect to get back the same array (i.e HitCurrent.arrayCurrent) between the computation of it into computeHit function and after the await call, but this is not the case.
How could I fix this issue ? I tried to pass by global variables, without success. Feel free to ask me further informations if you need it.

Let's rewrite your code just a little and rename some variables.
Literally, I've renamed the parameters of computeHit(..) to my_variable_1 and my_variable_2.
You may want to check/admit this does not change the code execution.
You should now understand why the HitCurrent in your async code block is never modified. Refer to simplified code block 3, which will yield 42. Hope it helps.
Code block 1 unchanged :
// Perform hit computer
(async () => {
// Wait computeHit function
await computeHit(HitCurrent, 'computer');
// Output array which is not the same than into computeHit
console.log('into async : HitCurrent.arrayCurrent', HitCurrent.arrayCurrent);
alert('into async() function');
})();
Code block 2 with renamed variables :
function computeHit(my_variable_1, my_variable_2) {
if (my_variable_2 == 'computer') {
return new Promise( resolve => {
// Creation of webworker
let firstWorker = new Worker(workerScript);
firstWorker.onmessage = function (event) {
resolve(event.data);
}
// Post current copy of my_variable_1, i.e my_variable_1
firstWorker.postMessage([my_variable_1, my_variable_1.playerCurrent, maxNodes]);
}).then(({result}) => {
// Get back game board of webworker
my_variable_1 = result.HitResult;
// Get back suggested hit computed by webworker
[a,b] = my_variable_1.coordPlayable;
// Drawing all lines from suggested hit (in 8 directions)
// my_variable_1.arrayCurrent is modified HERE !
for (k = 0; k < 8; k++) {
exploreHitLine(my_variable_1, a, b, k, 'drawing');
}
// Remove playable hits
cleanHits('playable', my_variable_1);
// Display current game
displayCurrentHit(my_variable_1);
// Output array which is good
alert('into computeHit function');
console.log('into promise - my_variable_1.arrayCurrent', my_variable_1.arrayCurrent);
})
}
}
Code block 3, simplified :
// Perform hit computer
var magicNumber = 42;
(async () => {
await computeHit(magicNumber, 'computer');
console.log('magic is ', magicNumber);
})();
function computeHit(my_variable_1, my_variable_2) {
if (my_variable_2 == 'computer') {
// Get back game board of webworker
my_variable_1 = 314159;
}
}

Assigning to a parameter within a function has no effect whatsoever on any variable used as the argument for that parameter in the call. Simplifying your code:
function computeHit(HitCurrent) {
HitCurrent = "something else";
}
let HitCurrent = "something";
computeHit(HitCurrent);
console.log(HitCurrent); // "something", not "something else"
There is no link whatsoever between the HitCurrent variable in computeHit(HitCurrent) and the HitCurrent parameter inside the call to computeHit, other than that the value of the first was read and passed into the function as the value of the second.
If you want to update HitCurrent based on what happens in computeHit, either:
Return the new HitCurrent and assign it back to the variable: HitCurrent = computeHit(...
Make HitCurrent refer to an object, and modify the object's state (e.g., use properties on it).

Related

Access and modify website variables using Javascript [duplicate]

I have a click event that is triggered from another place automatically for the first time. My problem is that it runs too soon, since the required variables are still being defined by Flash and web services. So right now I have:
(function ($) {
$(window).load(function(){
setTimeout(function(){
$('a.play').trigger("click");
}, 5000);
});
})(jQuery);
The problem is that 5 seconds for a person with a slow internet connection could be too fast and vice versa, for a person with a fast internet connection, it's too slow.
So how should I do the delay or timeout until someVariable is defined?
The following will keep looking for someVariable until it is found. It checks every 0.25 seconds.
function waitForElement(){
if(typeof someVariable !== "undefined"){
//variable exists, do what you want
}
else{
setTimeout(waitForElement, 250);
}
}
async, await implementation, improvement over #Toprak's answer
(async() => {
console.log("waiting for variable");
while(!window.hasOwnProperty("myVar")) // define the condition as you like
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("variable is defined");
})();
console.log("above code doesn't block main function stack");
After revisiting the OP's question. There is actually a better way to implement what was intended: "variable set callback". Although the below code only works if the desired variable is encapsulated by an object (or window) instead of declared by let or var (I left the first answer because I was just doing improvement over existing answers without actually reading the original question):
let obj = encapsulatedObject || window;
Object.defineProperty(obj, "myVar", {
configurable: true,
set(v){
Object.defineProperty(obj, "myVar", {
configurable: true, enumerable: true, writable: true, value: v });
console.log("window.myVar is defined");
}
});
see Object.defineProperty
or use es6 proxy (which is probably overkill)
If you are looking for more:
/**
* combining the two as suggested by #Emmanuel Mahuni,
* and showing an alternative to handle defineProperty setter and getter
*/
let obj = {} || window;
(async() => {
let _foo = await new Promise(res => {
Object.defineProperty(obj, "foo", { set: res });
});
console.log("obj.foo is defined with value:", _foo);
})();
/*
IMPORTANT: note that obj.foo is still undefined
the reason is out of scope of this question/answer
take a research of Object.defineProperty to see more
*/
// TEST CODE
console.log("test start");
setTimeout(async () => {
console.log("about to assign obj.foo");
obj.foo = "Hello World!";
// try uncomment the following line and compare the output
// await new Promise(res => setTimeout(res));
console.log("finished assigning obj.foo");
console.log("value of obj.foo:", obj.foo); // undefined
// console: obj.foo is defined with value: Hello World!
}, 2000);
I would prefer this code:
function checkVariable() {
if (variableLoaded == true) {
// Here is your next action
}
}
setTimeout(checkVariable, 1000);
I prefer something simple like this:
function waitFor(variable, callback) {
var interval = setInterval(function() {
if (window[variable]) {
clearInterval(interval);
callback();
}
}, 200);
}
And then to use it with your example variable of someVariable:
waitFor('someVariable', function() {
// do something here now that someVariable is defined
});
Note that there are various tweaks you can do. In the above setInterval call, I've passed 200 as how often the interval function should run. There is also an inherent delay of that amount of time (~200ms) before the variable is checked for -- in some cases, it's nice to check for it right away so there is no delay.
With Ecma Script 2017 You can use async-await and while together to do that
And while will not crash or lock the program even variable never be true
//First define some delay function which is called from async function
function __delay__(timer) {
return new Promise(resolve => {
timer = timer || 2000;
setTimeout(function () {
resolve();
}, timer);
});
};
//Then Declare Some Variable Global or In Scope
//Depends on you
let Variable = false;
//And define what ever you want with async fuction
async function some() {
while (!Variable)
await __delay__(1000);
//...code here because when Variable = true this function will
};
////////////////////////////////////////////////////////////
//In Your Case
//1.Define Global Variable For Check Statement
//2.Convert function to async like below
var isContinue = false;
setTimeout(async function () {
//STOPT THE FUNCTION UNTIL CONDITION IS CORRECT
while (!isContinue)
await __delay__(1000);
//WHEN CONDITION IS CORRECT THEN TRIGGER WILL CLICKED
$('a.play').trigger("click");
}, 1);
/////////////////////////////////////////////////////////////
Also you don't have to use setTimeout in this case just make ready function asynchronous...
You can use this:
var refreshIntervalId = null;
refreshIntervalId = setInterval(checkIfVariableIsSet, 1000);
var checkIfVariableIsSet = function()
{
if(typeof someVariable !== 'undefined'){
$('a.play').trigger("click");
clearInterval(refreshIntervalId);
}
};
Here's an example where all the logic for waiting until the variable is set gets deferred to a function which then invokes a callback that does everything else the program needs to do - if you need to load variables before doing anything else, this feels like a neat-ish way to do it, so you're separating the variable loading from everything else, while still ensuring 'everything else' is essentially a callback.
var loadUser = function(everythingElse){
var interval = setInterval(function(){
if(typeof CurrentUser.name !== 'undefined'){
$scope.username = CurrentUser.name;
clearInterval(interval);
everythingElse();
}
},1);
};
loadUser(function(){
//everything else
});
Instead of using the windows load event use the ready event on the document.
$(document).ready(function(){[...]});
This should fire when everything in the DOM is ready to go, including media content fully loaded.
Shorter way:
var queue = function (args){
typeof variableToCheck !== "undefined"? doSomething(args) : setTimeout(function () {queue(args)}, 2000);
};
You can also pass arguments
I have upvoted #dnuttle's answer, but ended up using the following strategy:
// On doc ready for modern browsers
document.addEventListener('DOMContentLoaded', (e) => {
// Scope all logic related to what you want to achieve by using a function
const waitForMyFunction = () => {
// Use a timeout id to identify your process and purge it when it's no longer needed
let timeoutID;
// Check if your function is defined, in this case by checking its type
if (typeof myFunction === 'function') {
// We no longer need to wait, purge the timeout id
window.clearTimeout(timeoutID);
// 'myFunction' is defined, invoke it with parameters, if any
myFunction('param1', 'param2');
} else {
// 'myFunction' is undefined, try again in 0.25 secs
timeoutID = window.setTimeout(waitForMyFunction, 250);
}
};
// Initialize
waitForMyFunction();
});
It is tested and working! ;)
Gist: https://gist.github.com/dreamyguy/f319f0b2bffb1f812cf8b7cae4abb47c
Object.defineProperty(window, 'propertyName', {
set: value => {
this._value = value;
// someAction();
},
get: () => this._value
});
or even if you just want this property to be passed as an argument to a function and don't need it to be defined on a global object:
Object.defineProperty(window, 'propertyName', { set: value => someAction(value) })
However, since in your example you seem to want to perform an action upon creation of a node, I would suggest you take a look at MutationObservers.
I have an adaptation of the answer by #dnuttle that I would suggest using.
The advantage of using a try-catch block is that if any part of the code you are trying to execute fails, the whole block fails. I find this useful because it gives you a kind of transaction; everything or nothing gets done.
You should never write code that could end up in an endless loop due to external factors. This is exactly what would happen if you were waiting for a response from an ajax request and the server doesn't respond. I think it's good practice to have a timeout for any questionable loops.
let time = 0; // Used to keep track of how long the loop runs
function waitForElement() {
try {
// I'm testing for an element, but this condition can be
// any reasonable condition
if (document.getElementById('test') === null) {
throw 'error';
}
// This is where you can do something with your variable
// document.getElementById('test').addEventListener....
// or call a function that uses your value
} catch (error) {
// Loop stops executing if not successful within about 5 seconds
if (time > 5000) {
// I return false on failure so I can easily check for failure
return false;
} else {
// Increment the time and call the function again
time += 250;
setTimeout(waitForElement, 250);
}
}
}
// Call the function after the definition, ensures that time is set
waitForElement();
You could have Flash call the function when it's done. I'm not sure what you mean by web services. I assume you have JavaScript code calling web services via Ajax, in which case you would know when they terminate. In the worst case, you could do a looping setTimeout that would check every 100 ms or so.
And the check for whether or not a variable is defined can be just if (myVariable) or safer: if(typeof myVariable == "undefined")
Very late to the party but I want to supply a more modern solution to any future developers looking at this question. It's based off of Toprak's answer but simplified to make it clearer as to what is happening.
<div>Result: <span id="result"></span></div>
<script>
var output = null;
// Define an asynchronous function which will not block where it is called.
async function test(){
// Create a promise with the await operator which instructs the async function to wait for the promise to complete.
await new Promise(function(resolve, reject){
// Execute the code that needs to be completed.
// In this case it is a timeout that takes 2 seconds before returning a result.
setTimeout(function(){
// Just call resolve() with the result wherever the code completes.
resolve("success output");
}, 2000);
// Just for reference, an 'error' has been included.
// It has a chance to occur before resolve() is called in this case, but normally it would only be used when your code fails.
setTimeout(function(){
// Use reject() if your code isn't successful.
reject("error output");
}, Math.random() * 4000);
})
.then(function(result){
// The result variable comes from the first argument of resolve().
output = result;
})
.catch(function(error){
// The error variable comes from the first argument of reject().
// Catch will also catch any unexpected errors that occur during execution.
// In this case, the output variable will be set to either of those results.
if (error) output = error;
});
// Set the content of the result span to output after the promise returns.
document.querySelector("#result").innerHTML = output;
}
// Execute the test async function.
test();
// Executes immediately after test is called.
document.querySelector("#result").innerHTML = "nothing yet";
</script>
Here's the code without comments for easy visual understanding.
var output = null;
async function test(){
await new Promise(function(resolve, reject){
setTimeout(function(){
resolve("success output");
}, 2000);
setTimeout(function(){
reject("error output");
}, Math.random() * 4000);
})
.then(function(result){
output = result;
})
.catch(function(error){
if (error) output = error;
});
document.querySelector("#result").innerHTML = output;
}
test();
document.querySelector("#result").innerHTML = "nothing yet";
On a final note, according to MDN, Promises are supported on all modern browsers with Internet Explorer being the only exception. This compatibility information is also supported by caniuse. However with Bootstrap 5 dropping support for Internet Explorer, and the new Edge based on webkit, it is unlikely to be an issue for most developers.
while (typeof myVar == void(0)) {
if ( typeof myVar != void(0)) {
console.log(myVar);
}
}
This makes use of the typeof operator which only returns undefined if variable is not declared yet. Should be applicable in every type of javascript.

Interrupting a function due to an event firing

Lets say I have the following function:
let x = 1
function countForever() {
setTimeout(function() {
console.log(x)
x = x +1
countForever()
});
}
We also have an object which contains an EventEmitter called e. e has a state, and if that state doesn't equal 3, we wish to kill our function. We can achieve this with the following:
let x = 1
function countForever() {
if (e.state != 3) return
setTimeout(function() {
console.log(x)
x = x +1
countForever()
});
}
This works. However my real, non example function has a lot more steps in it, and I've found myself continually if checking the state, 8-10x through the function.
Given e is an EventEmitter I would like to catch these changes when the state changes and kill the function. Luckily, e already has an event we can listen for:
e.on('state_changed' , function(new_state) {
// Kill countForever
})
How do I stop execution of this function from outside of its scope?
EDIT: I don't know why I wrote a sample function with a setTimeout, it seems I've been quite misleading. Here's a better one:
async function functionToKill() {
if (e.state != 3) return
thing1 = await functionThatTakesALongTime()
if (e.state != 3) return
thing2 = await secondFunctionThatTakesALongTime()
if (e.state != 3) return
thing3 = await thirdFunctionThatTakesALongTime()
//.....
if (e.state != 3) return
thing10 = await tenthFunctionThatTakesALongTime()
}
// e is an event emitter
e.on('state_changed' , function(new_state) {
// Kill/interrupt functionToKill
})
Effectively within the function I'm continually checking for the state over and over and returning if it's changed. I don't feel this is clean, and would like to do the equivalent of a return from an external call triggered by an eventEmitter
You don't give us a whole lot to go on in your sample function, but here's an idea with that code. You use a master promise that when rejected causes your sequence of await operations to abort:
// create deferred object so it can be rejected externally
Promise.Deferred = function() {
if (!(this instanceof Promise.Deferred)) {
return new Promise.Deferred();
}
let p = this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
this.then = this.promise.then.bind(p);
this.catch = this.promise.catch.bind(p);
if (this.promise.finally) {
this.finally = this.promise.finally.bind(p);
}
}
// shared promise, when rejected our function stops advancing to more operations
let killPromise = new Promise.Deferred();
function raceKill(p) {
return Promise.race(killPromise, p);
}
async function functionToKill() {
try {
thing1 = await raceKill(functionThatTakesALongTime());
thing2 = await raceKill(secondFunctionThatTakesALongTime());
thing3 = await raceKill(thirdFunctionThatTakesALongTime());
//.....
thing10 = await raceKill(tenthFunctionThatTakesALongTime());
} catch(e) {
// perhaps handle kill errors separately from actual function rejection errors
}
}
// e is an event emitter
e.on('state_changed' , function(new_state) {
// Kill/interrupt functionToKill
killPromise.reject(new Error("state_changed"));
})
This structure with Promise.race() has a bit of an advantage in that it doesn't even wait for functionThatTakesALongTime() to finish before aborting (when your pseudo-code would have been able to check e.state). It aborts immediately when you reject killPromise. That other asynchronous operation isn't magically cancelled. It will still do what it was going to do, but your functionToKill() won't wait around for it.
With actual code, there are probably more elegant ways to do this than using shared scope variables like killPromise, passing parameters, sharing something as object properties, etc... But, hopefully this shows you the general idea.
kill countForever? you can save the return value of setTimeout function with a variable such as timer, then clearTimeout(timer) when state_changed event fired. I don`t know if what I understand is right?

JS how to do something only once

For example:
// run this:
alert('Loading...');
// dont run this again:
alert('Loading...');
I don't want to ever repeat that.
How can I do this with convenience (preferably without using booleans)?
The standard way is to use a boolean flag.
But, if you have an aversion to booleans, you can do it by overwriting the function, thus ensuring it literally can never be called again.
function loadButOnlyOnce() {
console.log('This will only happen once!');
loadButOnlyOnce = function() {};
}
loadButOnlyOnce();
loadButOnlyOnce();
Your IDE will probably spew out warnings along the lines of "What are you doing, this is overwriting the function!". But it's your code, and you can do it if you want.
So, now you want a generic solution you can use with different functions? You can do this like so:
function allowOnlyOneCall(f) {
return function() {
f.apply(this, arguments);
f = function() {};
}
}
function myMethod(p1) {
console.log('myMethod is being invoked. p1 = ' + p1);
}
myMethod = allowOnlyOneCall(myMethod);
myMethod(5);
myMethod(5);
Here is one clean implementation you could use so you can avoid the usage of booleans for every single task you don't want to repeat:
var cache = [];
function do_once(task, fn) {
if(cache.indexOf(task) == -1) { // shorthand: !~cache.indexOf(task)
cache.push(task);
return fn();
}
}
Usage:
var alertLoading = alert.bind(null, "Loading...");
do_once("alert_loading", alertLoading); // will run
// later on...
do_once("alert_loading", alertLoading); // will not run again
This works as long as you give each task you don’t want to repeat a different name. Regardless of the function provided as the second argument to do_once, it will not run as long as do_once has already been called using the same task name.
First create a variable to store whether the function has already run or not (such as hasRun). Then simply store your functionality inside an if condition which checks that this variable is false. You would need to update this variable after running the logic for the first time.
This can be seen in the following:
hasRun = false;
document.getElementsByTagName('button')[0].addEventListener('click', function() {
if (!hasRun) {
console.log('Loading...'); // Gets run once
hasRun = true; // Set the flag to true so the conditional doesn't get entered again
} else {
console.log('The function already ran!'); // Runs every subsequent time
}
})
<button>Click me</button>
If you want a factory function that memoizes the result of calling a function with a single primitive value as a parameter, you should use a Map:
const once = (() => fn => {
const cache = new Map()
return value => {
if (!cache.has(value)) {
cache.set(value, fn(value))
}
return cache.get(value)
}
})()
function verboseSquare (value) {
console.log('expensive calculation')
return value * value
}
const squareOnce = once(verboseSquare)
console.log(squareOnce(4))
console.log(squareOnce(4)) // skipped work
console.log(squareOnce(5))
console.log(squareOnce(5)) // skipped work

What is a state machine in terms of JavaScript promises and C# asyc-await?

I'm currently looking at async-await in C#, and noticed similarities to JavaScript promises. Looking into this I see that JavaScript is also going to support async-await statements, and that there are similarities between this and promises (look at this blog post for example).
On a whim, I wondered what JavaScript's implementation of async-await was and found this question (Java Equivalent of C# async/await?).
The accepted answer suggests that async-await (and by extension, I guess, promises) are implementations of a 'state machine'.
Question: What is meant by a 'state machine' in terms of promises, and are JavaScript promises comparable to C#'s async-await?
JavaScript promises are comparable to C# Task objects which have a ContinueWith function that behaves like .then in JavaScript.
By "state machines" it is means that they are typically implemented by a state and a switch statement. The states are places the function can be at when it runs synchronously. I think it's better to see how such a transformation works in practice. For example let's say that your runtime only understands regular functions. An async function looks something like:
async function foo(x) {
let y = x + 5;
let a = await somethingAsync(y);
let b = await somethingAsync2(a);
return b;
}
Now, let's look at all the places the function can be when it executes a step synchronously:
async function foo(x) {
// 1. first stage, initial
let y = x + 5;
let a = await somethingAsync(y);
// 2. after first await
let b = await somethingAsync2(a);
// 3. after second await
return b;
// 4. done, with result `c`.
}
Now, since our runtime only understands synchronous functions - our compiler needs to do something to make that code into a synchronous function. We can make it a regular function and keep state perhaps?
let state = 1;
let waitedFor = null; // nothing waited for
let waitedForValue = null; // nothing to get from await yet.
function foo(x) {
switch(state) {
case 1: {
var y = x + 5;
var a;
waitedFor = somethingAsync(y); // set what we're waiting for
return;
}
case 2: {
var a = waitedForValue;
var b;
waitedFor = somethingAsync(a);
return;
}
case 3: {
b = waitedFor;
returnValue = b; // where do we put this?
return;
}
default: throw new Error("Shouldn't get here");
}
}
Now, it's somewhat useful, but doesn't do anything too interesting - we need to actually run this as a function. Let's put the state in a wrapper and automatically run the promises when they're resolved:
function foo(x) { // note, not async
// we keep our state
let state = 1, numStates = 3;
let waitedFor = null; // nothing waited for
let waitedForValue = null, returnValue = null; // nothing to get from await yet.
// and our modified function
function stateMachine() {
switch(state) {
case 1: {
var y = x + 5;
var a;
waitedFor = somethingAsync(y); // set what we're waiting for
return;
}
case 2: {
var a = waitedForValue;
var b;
waitedFor = somethingAsync(a);
return;
}
case 3: {
b = waitedFor;
returnValue = b; // where do we put this?
return;
}
default: throw new Error("Shouldn't get here");
}
// let's keep a promise for the return value;
let resolve, p = new Promise(r => resolve = r); // keep a reference to the resolve
// now let's kickStart it
Promise.resolve().then(function pump(value) {
stateMachine();
state++; // the next state has progressed
if(state === numStates) resolve(returnValue); // return the value
return Promise.resolve(waitedFor).then(pump);
});
return p; // return the promise
}
Effectively, the Promise.resolve().then(... part calls the stateMachine and waits for the value that's being awaited every time until it is at the final state at which point it resolves the (returned beforehand) promise.
This is effectively what Babel or TypeScript do with your code too. What the C# compiler does is very close - with the biggest difference is that it's put in a class.
Note we are ignoring conditionals, exceptions and loops here - it makes things a little bit more complicated but not much harder (you just need to handle each case separately).

RxJS: Cache Several XHR Calls without Mutability and Side Effects

I’m looking for an RxJS example how to cache a series of XHR calls (or other async operations), so the same call does not have to be repeated, while respecting immutability and with no side effects.
Here's a bare-bones, mutable example:
var dictionary = {}; // mutable
var click$ = Rx.Observable.fromEvent(document.querySelector('button'), 'click', function (evt) {
return Math.floor(Math.random() * 6) + 1; // click -> random number 1-6 (key)
})
.flatMap(getDefinition);
var clicksub = click$.subscribe(function (key) {
console.log(key);
});
function getDefinition (key) {
if ( dictionary[key] ) { // check dict. for key
console.log('from dictionary');
return Rx.Observable.return(dictionary[key]);
}
// key not found, mock up async operation, to be replaced with XHR
return Rx.Observable.fromCallback(function (key, cb) {
dictionary[key] = key; // side effect
cb(dictionary[key); // return definition
})(key);
}
JSBin Demo
Question: Is there a way to accomplish caching several similar async operations without resorting to using the dictionary variable, due to mutability and side effect?
I’ve looked at scan as a means to “collect” the XHR call results, but I don’t see how to handle an async operation within scan.
I think I’m dealing with two issues here: one is state management maintained by the event stream rather than kept in a variable, and the second is incorporating a conditional operation that may depend on an async operation, in the event stream flow.
Using the same technique than in a previous question (RxJS wait until promise resolved), you could use scan and add the http call as a part of the state you keep track of. This is untested but you should be able to easily adapt it for tests :
restCalls$ = click$
.scan(function (state, request){
var cache = state.cache;
if (cache.has(request)) {
return {cache : cache, restCallOrCachedValue$ : Rx.Observable.return(cache.get(request))}
}
else {
return {
cache : cache,
restCallOrCachedValue$ : Rx.Observable
.defer(function(){
return Rx.Observable
.fromPromise(executeRestCall(request))
.do(function(value){cache.add(request,value)})
})
}
}
}, {cache : new Cache(), restCallOrCachedValue$ : undefined})
.pluck('restCallOrCachedValue$')
.concatAll()
So basically you pass an observable which does the call down the stream or you directly return the value in the cache wrapped in an observable. In both cases, the relevant observables will be unwrapped in order by concatAll. Note how a cold observable is used to start the http call only at the time of the subscription (supposing that executeRestCall executes the call and returns immediately a promise).
Working from #user3743222's response, this code does cache the XHR calls:
// set up Dictionary object
function Dictionary () { this.dictionary = {} }
Dictionary.prototype.has = function (key) { return this.dictionary[key] }
Dictionary.prototype.add = function (key, value) { this.dictionary[key] = value }
Dictionary.prototype.get = function (key) { return this.dictionary[key] }
var definitions$ = click$
.scan(function (state, key) {
var dictionary = state.dictionary
// check for key in dict.
if ( dictionary.has(key) ) {
console.log('from dictionary')
return {
dictionary: dictionary,
def$: Rx.Observable.return(dictionary.get(key))
}
}
// key not found
else {
return {
dictionary: dictionary,
def$: Rx.Observable.fromPromise(XHRPromise(key))
.do(function (definition) { dictionary.add(key, definition) })
}
}
}, {dictionary : new Dictionary(), def$ : undefined})
.pluck('def$') // pull out definition stream
.concatAll() // flatten
Updated: In the // key not found block, executing the function XHRPromise (that returns a promise) passing in the key and the result passed to the RxJS method fromPromise. Next, chain a do method in which the definition is grabbed and added to the dictionary cache.
Follow up question: It appears we've removed the side effect issue, but is this still considered mutating when the definition and key are added to the dictionary?
Putting this here for archival purposes, the first iteration had this code, accomplishing the same caching procedure:
// ALTERNATIVE key not found
else {
var retdef = Rx.Observable.fromPromise(function () {
var prom = XHRPromise(key).then(function (dd) {
dictionary.add(key, dd) // add key/def to dict.
return dd
})
return prom
}()) // immediately invoked anon. func.
return { dictionary: dictionary, def$: retdef }
}
Here, using the function XHRPromise that returns a promise. Before returning that promise to the event stream, though, pull out a then method in which the promised definition is grabbed and added to the dictionary cache. After that the promise object is returned to the event stream through the RxJS method fromPromise.
To get access to the definition returned from the XHR call, so it can be added to the dictionary cache, an anonymous, immediately invoked function is used.

Categories

Resources