What to do when one stream depends on the value from another? - javascript

I'm new to Rx.js and looking for some basic help with FRP concepts. I have one stream using generateWithRelativeTime that keeps a game loop running, and a stream that keeps track of the screen resolution. My problem with my current code is that my game loop only runs when my screen resolution changes.
The stream generated by my model function requires the latest value from res$.
function model(res$) {
return res$.map(res => {
const rows = ceil(res[1] / CELL.HEIGHT)+1
const cols = ceil(res[0] / CELL.WIDTH)+1
return Observable.generateWithRelativeTime(
initWorld(rows, cols, 1.618),
noOp,
world => step(world),
noOp,
_ => 100,
Rx.Scheduler.requestAnimationFrame
).timeInterval()
});
}
function intent() {
return Observable.fromEvent(window, 'resize')
.map(e => [e.currentTarget.innerWidth, e.currentTarget.innerHeight])
.debounce(100, Rx.Scheduler.requestAnimationFrame)
.startWith([window.innerWidth, window.innerHeight])
}
model(intent())

Welcome to the joy of FRP. As it is, it is difficult to understand your question. How What to do when one stream depends on the value from another? is connected to My problem with my current code is that my game loop only runs when my screen resolution changes.? You give the actual behavior, what is your expected behavior?
For my limited understanding of the question, I think that you need to replace this line return res$.map(res => { by return res$.flatMapLatest(res => {, i.e. use the operator flatMapLatest instead of the map operator.
To understand the flatMap operator, I recommend you to review the following resources : The introduction to reactive programming you've been missing, the illustrated marbles and the official documentation.
PS : I see that you seem to follow a MODEL-VIEW-INTENT architecture. Out of curiosity, are you experimenting with cyclejs?

Related

Non-blocking UI while Search/Sort/Filter operations

I have a large list of items (3000 - 5000) to display. The user may filter / sort them in order to display only a particular subset. My initial implementation was essentially:
import { filterItems, sortItems }
const items = [...];
const results = flow([
filterItems,
sortItems,
])(items);
...where the imported functions are basically wrappers around array .sort and .filter ( + a lot of custom options). This works, but it blocks the UI. I'm looking for recommendations on how to proceed while still utilizing these two core functions. I feel I've two general paths:
1) JS scheduling / time-slicing
How might I break up the filter/sort functions to accommodate this? I've tried using something like this queue scheduler + slightly modifying sortItems to yield each loop, but as the whole thing is in a sort callback it does not work:
// simplified for brevity
function sortItems(items, context) { // context comes from a Scheduler
return items.sort((a, b) => {
context.yield(); // can I return control back here ...?
return (+!a - +!b) || (a > b ? 1 : -1);
}
}
I want to try other variations here on this theme (ie. setTimeout, rAF etc) but as there is no "unit of work" or "step" function, I'm wondering if I'd need to implement my own sorting/filtering using a for loop and not rely on [].filter / [].sort. Are there performance implications to this?
2) Web-worker
This is another option. However, for maintainability I'd prefer to re-use filterItems, sortItems without having to copy/paste them into a worker file. Is there a way to do this? I'm aware of inlining the worker i.e. using URL + Blobs, or Greenlet... but I've not been able to get it to work.
let results = greenlet(async (items) => {
const filtered = filterItems(items); // <-- i realize this is not possible (and doesn't work)
...
});
Relatedly - is it possible to somehow use an imported function via toString() in a worker? i.e filterItems.toString() + an inlining method?
If you have general advice or can share experience in similar ventures (or if i'm doing it wrong), please let me know. Thanks in advance ~

Angular 4 weird error: query doesn't end when -not- showing a message toast

Here this is, my weirdest error in my whole programming career. I've been struggling through this, yet I can't find what's going on in this code. It just seems not to make any sense in any way.
I'm using the following tools:
Ionic 3
Angular 4
Typescript / ES6
I'm trying to do a method, "assignChat(user)", which assigns a chat to a user. It has to use several APIs, geolocation... it's a big method, actually. That's why I've split it in two parts connected by promises, and used them after, so my method looks pretty much like this:
assignChat(user){
const getLocationName = () => {
return new Promise((resolve,reject) => {
// 30 lines of code
});
}
const assignOrCreateChat= (area) => {
return new Promise((resolve,reject) => {
// 40 lines of code
});
}
const getLocationName = () => {
return new Promise((resolve,reject) => {
// 30 lines of code
});
}
// then I use the inner functions here and write an extra 60-70 lines of code
}
Ok! This works neat. Didn't have much problems with this algorithm after some several testing, although is quite heavy and takes ~0.5s to properly execute, finish it's queries, and show the result.
Thing is... I had some toasts displaying some information, like where you're located. I wanted to remove them, and started by this one, in the inner function getLocationName(). This is the code I want to talk you about:
const getLocationName = () => {
return new Promise( (resolve, reject) => {
const ADDRESS_LEVEL = 2;
this.reverseGeocode(ADDRESS_LEVEL).then( address => {
---> this.toastify("You have been located at: "+address, 1500);
let query = new Parse.Query("PoliticalArea");
// more code
The line I marked with an arrow, is the line which is giving me problems. I mean, you probably think the code fails because of the line, but it's totally the oposite! If I remove that line, the algorithm suddenly stops working and fails to display any result.
The "toastify" method is a quick way I did for myself for displaying toasts. It works well, actually! This is the implementation:
toastify(message, duration){
this.toastCtrl.create({
message: message,
duration: duration
}).present();
}
Not like the most dangerous method. Well, in fact, it seems that the code won't work without it. If I comment the line, or erase it, I never get any result, or any error, from the big algorithm I showed you before. I've got every possible exception catched, although the API connectors don't have timeout, but it's like it gets stuck every time it doesn't display the toast.
I just don't understand what's going on. Seems like a very serious thing the Angular team should look into, in my very honest opinion.
Any idea of what kind of black magic is going there?
UPDATE:
Some further info: when I navigate through the "bugged" view (without the toastify line, and therefore not displaying the chat result), and per example, click in another chat (which pushes a view into the Navigation Controller), it somehow starts showing the chat result I expected. When I pop the new view from the navCtrl, and get back to the page, the expected result is now visible.
Is this some problem with angular watches?
Ok, the solution was not obvious.
It seems that the view was being rendered before the task completed. It was a tough task, so maybe that's the reason why Angular didn't work properly. Tried executing it both in the constructor and in ionViewDidEnter(), though nothing worked.
My final solution was to force component's re-rendering, through ApplicationRef, using the .tick() method at the dead end of my method.
That fixed it all!

Handling assertions in a custom helper

I've started playing around with CodeceptJs and I've got it up working quite easily. I'm currently using it with NightmareJs and all seems fine.
The specific area I'm testing is a gallery that fetches data from an interface via JSONP creating a list of images wrapped in <div>s.
A portion of the tests I'm implementing is like the following:
Feature('gallery')
Scenario('clicking on an element adds "selected" class', (I) => {
I.amOnPage('/')
I.seeElement('#gallery .col-md-3')
I.click('#gallery .col-md-3')
I.seeElement('#gallery .selected')
})
Now since the elements can be any number, it's currently silently using the first element, but in order to give it a bit more entropy I wanted to pick an element at random, something like the following
Scenario('clicking on an element adds "selected" class', (I) => {
I.amOnPage('/')
I.seeMoreThanElements('#gallery .col-md-3', 1)
I.clickOnRandomElement('#gallery .col-md-3')
I.seeElement('#gallery .selected')
})
Or even better, if I could grab the list of elements so I can decide which one to click on, like:
Scenario('clicking on an element adds "selected" class', (I) => {
I.amOnPage('/')
I.seeMoreThanElements('#gallery .col-md-3', 1)
const elements = I.grabRandomElement('#gallery .col-md-3')
const random = getRandomInt(1, elements.length)
I.click(`#gallery .col-md-3:nth-child(${random})`)
I.seeElement(`#gallery .col-md-3.selected:nth-child(${random})`)
})
The current helpers available don't allow me to perform some particular actions, so I started implementing a custom handler as described in the guide at http://codecept.io/helpers/
In my configuration I have the following:
"helpers": {
"Nightmare": {
"url": "http://localhost:3000"
},
"DOMElements": {
"require": "./__tests__/helpers/domelements_helper.js"
}
}
and domelements_helper.js currently looks like the following:
'use strict'
let assert = require('assert')
class DOMElements extends Helper {
seeMoreThanElements (locator, count) {
this.helpers['Nightmare']._locate(locator).then(function (els) {
return assert(els.length >= count, `Found more than ${count} elements`)
})
}
}
module.exports = DOMElements
This doesn't - clearly - work. This is where I'm getting a bit confused.
First of all, I'm using the default Node.js assertion library, and if there's any need I'm happy to move over to something more robust like Protractor or Chai-as-promised, but the slimmer the better.
Secondly, the documentation clearly states the following:
any helper method should return a value in order to be added to promise chain
Which doesn't really make sense... should I return a promise or should I handle the whole thing within the then() statement? As returning a basic value doesn't really do much. Even then, how do I handle failed assertions?
I've also seen a Nightmare clientscript in the code base but I have no idea if it's of any use for my case, as I've just started digging through the code base in order to understand a little bit better how to customise and extend CodeceptJs.
Any pointers are really appreciated
since nobody seems to have got this far, I'm going to add an answer as I seem to have found how this thing works by going through the codebase and understand a bit more how it works.
tl;dr: the quick solution is the following:
/* __tests__/helpers/domelements_helper.js */
const assert = require('assert')
class DOMElements extends Helper {
seeMoreThanElements (locator, count) {
return this.helpers['Nightmare']._locate(locator)
.then((elementsArray) => {
if (elementsArray.length < count) {
return assert.fail(elementsArray.length, count, `Found more than ${count} elements`)
}
})
}
}
module.exports = DOMElements
The way the whole thing works is by promises and you have to handle failure appropriately so that the whole system can fail gracefully (sorta).
In particular _locate() returns a promise and everything has to be handled asynchronously, although by design this seems to be quite awkward and it's making things particularly hard to implement, at least in the current state.

How do reactive streams in JS work?

I'm novice in reactive streams and now trying to understand them. The idea looks pretty clear and simple, but on practice I can't understand what's really going on there.
For now I'm playing with most.js, trying to implement a simple dispatcher. The scan method seems to be exactly what I need for this.
My code:
var dispatch;
// expose method for pushing events to stream:
var events = require("most").create(add => dispatch = add);
// initialize stream, so callback in `create` above is actually called
events.drain();
events.observe(v => console.log("new event", v));
dispatch(1);
var scaner = events.scan(
(state, patch) => {
console.log("scaner", patch);
// update state here
return state;
},
{ foo: 0 }
);
scaner.observe(v => console.log("scaner state", v));
dispatch(2);
As I understand, the first observer should be called twice (once per event), and scaner callback and second observer – once each (because they were added after triggering first event).
On practice, however, console shows this:
new event 1
new event 2
scaner state { foo: 0 }
Scaner is never called, no matter how much events I push in stream.
But if I remove first dispatch call (before creating scaner), everything works just as I expected.
Why is this? I'm reading docs, reading articles, but so far didn't found anything even similar to this problem. Where am I wrong in my suggestions?
Most probably, you have studied examples like this from the API:
most.from(['a', 'b', 'c', 'd'])
.scan(function(string, letter) {
return string + letter;
}, '')
.forEach(console.log.bind(console));
They are suggesting a step-by-step execution like this:
Get an array ['a', 'b', 'c', 'd'] and feed its values into the stream.
The values fed are transformed by scan().
... and consumed by forEach().
But this is not entirely true. This is why your code doesn't work.
Here in the most.js source code, you see at line 1340 ff.:
exports.from = from;
function from(a) {
if(Array.isArray(a) || isArrayLike(a)) {
return fromArray(a);
}
...
So from() is forwarding to some fromArray(). Then, fromArray() (below in the code) is creating a new Stream:
...
function fromArray (a) {
return new Stream(new ArraySource(a));
}
...
If you follow through, you will come from Stream to sink.event(0, array[i]);, having 0 for timeout millis. There is no setTimeout in the code, but if you search the code further for .event = function, you will find a lot of additional code that uncovers more. Specially, around line 4692 there is the Scheduler with delay() and timestamps.
To sum it up: the array in the example above is fed into the stream asynchronously, after some time, even if the time seems to be 0 millis.
Which means you have to assume that somehow, the stream is first built, and then used. Even if the program code doesn't look that way. But hey, isn't it always the target to hide complexity :-) ?
Now you can check this with your own code. Here is a fiddle based on your snippet:
https://jsfiddle.net/aak18y0m/1/
Look at your dispatch() calls in the fiddle. I have wrapped them with setTimeout():
setTimeout( function() { dispatch( 1 /* or 2 */); }, 0);
By doing so, I force them also to be asynchronous calls, like the array values in the example actually are.
In order to run the fiddle, you need to open the browser debugger (to see the console) and then press the run button above. The console output shows that your scanner is now called three times:
doc ready
(index):61 Most loaded: [object Object]
(index):82 scanner state Object {foo: 0}
(index):75 scanner! 1
(index):82 scanner state Object {foo: 0}
(index):75 scanner! 2
(index):82 scanner state Object {foo: 0}
First for drain(), then for each event.
You can also reach a valid result (but it's not the same behind scenes) if you use dispatch() synchronously, having them added at the end, after JavaScript was able to build the whole stream. Just uncomment the lines after // Alternative solution, run again and watch the result.
Well, my question appears to be not so general as it sounds. It's just a lib-specific one.
First – approach from topic is not valid for most.js. They argue to 'take a declarative, rather than imperative, approach'.
Second – I tried Kefir.js lib, and with it code from topic works perfect. Just works. Even more, the same approach which is not supported in most.js, is explicitly recommended for Kefir.js.
So, the problem is in a particular lib implementation, not in my head.

Using jasmine the correct way or creating a testAll-spec

I have to say I'm kinda stuck with using jasmine(-node) in (most probably) a slightly wrong way. Currently I have jasmine tests so, that they need to be run in a correct order. I would at the moment need to have a file that can collect and initiate the different jasmine-files in the correct order.
Now I appreciate advice, which show me other ways of making the testing work well and not necessarily just fix this immediate problem, my testing skills are fairly limited, mostly because as a long time solo-coder I haven't had that much use for them.
So the actual problem is that I have test-specs:
mapGenerator-spec.js // Initializes / generates a clean map.
orders-spec.js // Simulates orders given by players (turn based)
map-spec.js // tests the part which is used front-end and backend, creating map from components
moveOrders-spec.js // Generates a part of the turn by resolving orders -> moving units.
So simply the logic in a game is that first generates a map, order generate orders given by players
These need to be executed in the precise order, because they create database-entries that are dependant on the previous test. I would like to keep the tests as product-ready / real as possible and not try to skip the database-inserts / fetches. There is a separate test-database, where the tests are generated.
So can you advice me, what you think is the correct way to do this (if this is not a good way) and / or advice me, if needed, how can I sum up these tests to one collection test "testAll-spec.js", which would run the tests synchronously.
If you need something to happen before you run a test, then you should use beforeEach().
You can also nest jasmine tests, and run a beforeEach() within each test, as below:
describe('testInitialiser', () => {
var firstVariable;
beforeEach(() => {
firstVariable = 1;
});
it('should set firstVariable to 1', () => {
expect(firstVariable).toBe(1);
});
// this is a nested test - these tests will run after the above beforeEach.
describe('nested test ', () => {
// all tests within this describe will have access to firstVariable.
var secondVariable;
beforeEach(() => {
secondVariable = firstVariable + 1;
});
it('should set secondVariable to firstVariable + 1 ', () => {
expect(secondVariable).toBe(2);
});
});
});
Hope this helps.

Categories

Resources