Consider a simple for of:
for (const elem of document.getElementsByTagName('*') {
// do something with elem
}
does getElementsByTagName evaluated only once or on each iteration ?
thx!
In this case, it's evaluated once to obtain an iterable, which it then uses to obtain an iterator. It reuses that iterator to grab all the values and pass them to your for block. It's very similar to doing the following with a generator function:
function* getIntegers(max) {
for (let i = 0; i <= max; i++) {
yield i;
}
}
const iterator = getIntegers(15);
while (true) {
const { done, value } = iterator.next();
if (done) {
break;
}
console.log(value);
}
As noted by loganfsmyth, generator functions return an iterator directly. Note: generator functions can also be used with the for..of construct.
See this article on MDN for more info.
Related
Having just moved from thunks to sagas I'm trying to find the best way to call setTimeout and then from within that function call another function (in this case corewar.step()). This was my original code which works as I'd expect.
runner = window.setInterval(() => {
for(let i = 0; i < processRate; i++) {
corewar.step()
}
operations += processRate;
}, 1000/60)
This code is inside a saga and I believe that I should be able to wrap function calls within call as I've done in other areas in the application.
I've tried wrapping the setInterval call in a call and leaving everything else as it is, which results in step() never being called.
runner = yield call(window.setInterval, () => {
for(let i = 0; i < processRate; i++) {
corewar.step()
}
operations += processRate;
}, 1000/60)
I've tried, leaving the setInterval as it is and wrapping the step() function in a call and changing the anonymous function signature to function* which also results in step() never being called.
runner = window.setInterval(function*() {
for(let i = 0; i < processRate; i++) {
yield call([corewar, corewar.step])
}
operations += processRate;
}, 1000/60)
Finally, I've tried wrapping both, which again results in step() never being called.
runner = yield call(window.setInterval, function*() {
for(let i = 0; i < processRate; i++) {
yield call([corewar, corewar.step])
}
operations += processRate;
}, 1000/60)
It feels like I'm missing something here so my question is, should I need to wrap these functions up in call at all or is this wrong?
The follow on question if I am supposed to wrap the outer setInterval in a call would be how should I be defining a function as a parameter to call which also wants to yield either a put or call itself?
There is a section in the saga-redux docs called "Using the eventChannel factory to connect to external events", that suggests using channels.
This section is also providing an example for a setInterval implementation:
import { eventChannel, END } from 'redux-saga'
function countdown(secs) {
return eventChannel(emitter => {
const iv = setInterval(() => {
secs -= 1
if (secs > 0) {
emitter(secs)
} else {
// this causes the channel to close
emitter(END)
}
}, 1000);
// The subscriber must return an unsubscribe function
return () => {
clearInterval(iv)
}
}
)
}
You would then use yield call and yield takeEvery to set it up:
const channel = yield call(countdown, 10);
yield takeEvery(channel, function* (secs) {
// Do your magic..
});
const anotherSaga = function * () {
const runner = yield call(setInterval, () => {
console.log('yes');
}, 1000);
console.log(runner);
}
This works pretty fine for me. In your second snippet there is a double ) at the end where should be only one.
A little late to the party here but this is the top search result for the question of setting a timer in a saga. There's an alternate solution due to the nature of sagas. From here.
I adapted this so:
function* callSelfOnTimer({ value }) {
// Do your work here
...
// If still true call yourself in 2 seconds
if (value) {
yield delay(2000);
yield call(callSelfOnTimer, { value });
}
}
For this to work you also need to add this:
const delay = (ms) => new Promise(res => setTimeout(res, ms))
function* callSelfOnTimer({ value }) {
// Do your work here
...
// If still true call yourself in 2 seconds
if (value) {
yield delay(2000);
yield call(callSelfOnTimer, { value });
}
}
I haven't found a way yet to properly handle methods in objects when calling Utilities.jsonStringify(). Basically, I cannot use my object after I retrieve it from the CacheService and apply Utilities.jsonParse() to it.
Does anyone have a hint ?
Thanks in advance.
Marc
json does not include functions when stringifying/parsing. You have to use something homebrewn like:
function func2String(obj) {
var res={};
for (x in obj) {
var value=obj[x];
res[x]=(typeof(value)=='function')?value.toString():value;
}
return res;
}
function string2Func (obj) {
var res={};
for (x in obj) {
var value=obj[x];
if(typeof(value)!='string') {
res[x]=value;
}
else {
res[x]=(value.substring(0,9)=='\nfunction')?eval('('+value+')'):value;
}
}
return res;
}
usage:
var obj=string2Func (Utilities.jsonParse(q.diff));
var str=Utilities.jsonStringify(func2String(diff));
Of course the unpacked funcs lost all their closures.
If I have a number of functions that take json object parameters, does it make any difference whether I assign them to a variable before using them inside the function:
Function doSomething(data){
var abc = data;
abc.filter….etc.
}
Vs.
Function doSomething(data){
Data.filter….etc
}
is one way better than the other?
This makes no difference and it your example, the new variable is redundant. It is good practice not to create extra variables. It might be useful to do this if your JSON is heavily nested.
data = { foo: { bar: { baz: [] } } }
function doSomething(data) {
var innerData = data.bar.baz;
for(var i=0; i<innerData.length; i+) {
// Whatever.
}
}
This will save you having to reference data.foo.bar.baz all the time.
Yes, it is better not to create the useless extra variable.
It is completely redundant to create the abc variable in the first example.
Consider how it's really evaluated:
function doSomething() {
var data = arguments[0];
var abc = data; //why?
}
Render attachment names from couchdb without conversion using handlebars or mustache template.
{
"_id":"123",
"_attachments":{
"evil.jpg":{
"content_type":"image/jpeg",
"revpos":32,
"digest":"md5-CKtT5WWRLkmGDD3/DhK6FQ==",
"length":41915,
"stub":true
}
}
}
I think this is duplicate of Getting key's in handlebar.
// based on the `#each` helper, requires jQuery (for jQuery.extend)
Handlebars.registerHelper('each_hash', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var ret = "";
if(typeof context === "object") {
for(var key in context) {
if(context.hasOwnProperty(key)) {
// clone the context so it's not
// modified by the template-engine when
// setting "_key"
var ctx = jQuery.extend(
{"_key":key},
context[key]);
ret = ret + fn(ctx);
}
}
} else {
ret = inverse(this);
}
return ret;
});
The developers of handlebars are discussing putting this in
That helper should do the job anyway if you just want to add it. Your template would be like this.
{{#each_hash _attachments}}
{{_key}} - {{content_type}}
{{else}}
You didn't pass in an object!
{{/each_hash}}
The helper essentially just iterates through the object and does the conversion on the fly. It iterates through the object and adds the key as the _key variable. You don't have to include the else statement and it will return nothing by default.
Why do you think the code below does not work?
What would you change/add to make it work?
Any help is appreciated..
function TraceIt(message:String, num:int)
{
trace(message, num);
}
function aa(f:Function, ...args):void
{
bb(f, args);
}
aa(TraceIt, "test", 1);
var func:Function = null;
var argum:Array = null;
function bb(f:Function, ...args):void
{
func = f;
argum = args;
exec();
}
function exec()
{
func.apply(null, argum);
}
I get an ArgumentError (Error #1063):
Argument count mismatch on test_fla::MainTimeline/TraceIt(). Expected 2, got 1.
..so, the passed parameter (argum) fails to provide all passed arguments..
..Please keep the function structure (traffic) intact.. I need a solution using the same functions in the same order.. I have to pass the args to a variable and use them in the exec() method above..
regards
Ok, here is the solution.. after breaking my head : )
function TraceIt(message:String, num:int)
{
trace(message, num);
}
function aa(f:Function=null, ...args):void
{
var newArgs:Array = args as Array;
newArgs.unshift(f);
bb.apply(null, newArgs);
}
aa(TraceIt, "test", 1);
var func:Function = null;
var argum:*;
function bb(f:Function=null, ...args):void
{
func = f;
argum = args as Array;
exec();
}
function exec():void
{
if (func == null) { return; }
func.apply(this, argum);
}
This way, you can pass arguments as variables to a different function and execute them..
Thanks to everyone taking the time to help...
When TraceIt() eventually gets called, it's being called with 1 Array parameter, not a String and int parameters.
You could change TraceIt() to:
function TraceIt(args:Array)
{
trace(args[0], args[1]);
}
Or you could change exec() to:
function exec()
{
func.apply(null, argum[0].toString().split(","));
}
...as it appears when you pass "test", 1, you end up with array whose first value is "test,1". This solution doesn't work beyond the trivial case, though.
Change your bb function to look like this:
function bb(f:Function, args:Array):void
{
func = f;
argum = args;
exec();
}
As you have it now, it accepts a variable number of arguments, but you are passing in an array(of the arguments) from aa.