I am trying to use a clean arrow function that calls yield fork(....). I have a lot of places this happens. I am trying to do this:
const { ids } = yield take('REQUEST');
// ids is array of numbers [1, 2, 3]
ids.forEach(id => yield fork(requestWorker, id)); ////// this is what im trying to do - this is pseudo-code i know you cant use in non-generators (non-super-star functions)
However this does not work and i am having to do:
for (const id of ids) {
yield fork(requestWorker, id);
}
instead of the:
ids.forEach(id => yield fork(requestWorker, id)); // this is pseudo-code i know you cant use in non-generators (non-super-star functions)
Is there a cleaner way then a for-of loop? Like this arrow function method?
You could yield another generator
function* take(x) {
var id = /* ... */,
requesWroker = /* ... */;
yield* fork(requesWroker, id);
}
var all = [...take('REQUEST')];
Related
For example, I need to cache last task outside of the loop (redux-saga).
export default function* () {
let lastTask;
while (true) {
const action = yield take(someActions);
// some conditions there
if (lastTask) {
yield cancel(lastTask);
}
lastTask = yield fork(send(action));
}
}
What pattern should I use there to make it in a functional immutable way without let?
One option is to have the generator recursively yield* itself:
function* gen (lastTask) {
const action = yield take(someActions);
// some conditions there
if (lastTask) {
yield cancel(lastTask);
}
const nextLastTask = yield fork(send(action));
yield* gen(nextLastTask);
}
export default gen;
But note that nothing in your original code (or this code) involves mutation - the problem with let is that it permits reassignment, which is somewhat similar, but not the same thing. (Best to avoid mutation and reassignment, if possible, as long as the code remains easy to read)
Make sure the consumer calls gen with no arguments, of course.
Really sorry if the title does not make sense. Not sure how to make my question short
What I am wondering is,
I have a recursive function, well doesn't have to be recursive function just when I am doing this function, I wondered if it can be reused in a more flexiable way.
my function looks like this runAxios(ele, api) is the function I wonder if can be reused
const ct = (arr, num, res) => {
const promises = [];
for(let i = 0; i < num; i++){
const ele = arr.shift(); // take out 1st array, literally take out
if(ele){
promises.push(
runAxios(ele, api) // this is the function I am wondering if can be reused
)
}
}
Promise.all.......
};
if runAxios(ele, api) can be anything then I believe this ct can be a lot more flexiable?
I am wondering if it could it something like
const ct = (arr, num, res, fx) => {
const promises = [];
for loop......
if(ele){
promises.push(
fx // this is then passed as any other function other than just a fixed `axios function` that I wrote
)
}
}
Promise.all........
};
when I first tried it, I realized this will not work because runAxios's first parameter is done inside the loop which means the variable does not exist yet until it's inside the function itself.
Just being curious if there is such way to easily do it that I just don't know how or it's actually not possible?
Thanks in advance for any advices.
You were very close with your approach. It should look something like this:
const someFunction = (x) => new Promise((resolve) => resolve(x));
const ct = (arr, fn) => {
const promises = arr.filter(x => x).map(x => fn(x));
Promise.all(promises).then(x => console.log(x));
};
ct([1, 2, 3], someFunction);
(I also took the liberty to replace your loop with a more compact approach.)
Of course it can be done. You just have to call the function passed with the required paramemeters. You will calculate the ele parameter and then pass it to the function. A generic example to see the way it works is shown below:
const functionToBeCalled = (parameter1, parameter2) => {
console.log(parameter1 + parameter2);
}
const ct = (fx) => {
//..code
let ele = 1;
fx(ele, 2);
//..code
};
ct(functionToBeCalled);
I'm learning about generators and and iterators and I can't figure out, nor find any details on how to pass a value back to a generator object when iterating over the object using a for of loop. I'd like to pass the counter value back on each iteration. How is this possible?
function generate(time, id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`ALERT: ${id}`)
resolve(`${id} success.`)
}, time)
})
}
function* generator() {
const testA = yield generate(3000, 'A')
console.log(`Returned: ${testA}`)
const testB = yield generate(500, 'B')
console.log(`Returned: ${testB}`)
const testC = yield generate(300, 'C')
console.log(`Returned: ${testC}`)
}
async function test() {
let counter = 1
const genObj = generator()
// await genObj.next(counter).value
// counter += 1
// await genObj.next(counter).value
for (x of genObj) {
console.log('From here.')
const t = await x
counter += 1
console.log(t)
}
}
test()
By the spec this is not possible as no arguments are passed to the next() method internally when used in iterator loops like for ... of
https://www.ecma-international.org/ecma-262/6.0/#sec-iterator-interface
The for-of statement and other common users of Iterators do not pass
any arguments, so Iterator objects that expect to be used in such a
manner must be prepared to deal with being called with no arguments.
So in your case you would have to do a loop where you explicitly call next()
var A = {
demo : function() * {
/* Some logic here, but no yield is used */
}
}
What is the use of a generator method that does not yield anything?
Have you ever used something like this? What was the use case?
It's quite the same case like an empty function - someone wants to call a function, but you have nothing to do.
Similarly, an empty generator function is a function which creates a generator that does nothing. It does represent the empty sequence. However, a generator function that doesn't yield isn't necessarily empty - it can still do something and have a result value, but there simply are no intermediate results.
The following code prints 'someValue' on the response every 100 ms for 5 seconds. It does not use yield.
const Koa = require('koa');
const through = require('through');
(new Koa()).use(function *(){
const tr = through();
setInterval(() => tr.write('someValue\n'), 100);
setTimeout(tr.end, 5000);
this.body = tr;
}).listen(3003, () => {});
Access with: curl localhost:3003
Learn Generators - 4 » CATCH ERROR!
The solution uses a for loop but I just couldn't find anything in MDN - Iteration Protocols that refers to yield within callbacks.
I'm going to guess the answer is just don't do that but thanks in advance if anyone has the time or inclination to provide an explanation!
Code:
function *upper (items) {
items.map(function (item) {
try {
yield item.toUpperCase()
} catch (e) {
yield 'null'
}
}
}
var badItems = ['a', 'B', 1, 'c']
for (var item of upper(badItems)) {
console.log(item)
}
// want to log: A, B, null, C
Error:
⇒ learn-generators run catch-error-map.js
/Users/gyaresu/programming/projects/nodeschool/learn-generators/catch-error-map.js:4
yield item.toUpperCase() // error below
^^^^
SyntaxError: Unexpected identifier
at exports.runInThisContext (vm.js:73:16)
at Module._compile (module.js:443:25)
at Object.Module._extensions..js (module.js:478:10)
at Module.load (module.js:355:32)
at Function.Module._load (module.js:310:12)
at Function.Module.runMain (module.js:501:10)
at startup (node.js:129:16)
at node.js:814:3
Even my editor knows this is a terrible idea...
Disclaimer: I'm the author of Learn generators workshopper.
Answer by #slebetman is kinda correct and I also can add more:
Yes, MDN - Iteration Protocol doesn't refer directly about yield within callbacks.
But, it tell us about importance from where you yield item, because you can only use yield inside generators. See MDN - Iterables docs to find out more.
#marocchino suggest just fine solution iterate over Array that was changed after map:
function *upper (items) {
yield* items.map(function (item) {
try {
return item.toUpperCase();
} catch (e) {
return null;
}
});
}
We can do it, because Array has iteration mechanism, see Array.prototype[##iterator]().
var bad_items = ['a', 'B', 1, 'c'];
for (let item of bad_items) {
console.log(item); // a B 1 c
}
Array.prototype.map doesn't have default iteration behavior, so we couldn't iterate over it.
But generators is not just iterators. Every generator is an iterator, but not vice versa. Generators allows you to customize iteration (and not only) process by calling yield keyword. You can play and see the difference between generators/iterators here:
Demo: babel/repl.
One problem is yield yields just one level to the function's caller. So when you yield in a callback it may not do what you think it does:
// The following yield:
function *upper (items) { // <---- does not yield here
items.map(function (item) { // <----- instead it yields here
try {
yield item.toUpperCase()
} catch (e) {
yield 'null'
}
}
}
So in the code above, you have absolutely no access to the yielded value. Array.prototype.map does have access to the yielded value. And if you were the person who wrote the code for .map() you can get that value. But since you're not the person who wrote Array.prototype.map, and since the person who wrote Array.prototype.map doesn't re-yield the yielded value, you don't get access to the yielded value(s) at all (and hopefully they will be all garbage collected).
Can we make it work?
Let's see if we can make yield work in callbacks. We can probably write a function that behaves like .map() for generators:
// WARNING: UNTESTED!
function *mapGen (arr,callback) {
for (var i=0; i<arr.length; i++) {
yield callback(arr[i])
}
}
Then you can use it like this:
mapGen(items,function (item) {
yield item.toUpperCase();
});
Or if you're brave you can extend Array.prototype:
// WARNING: UNTESTED!
Array.prototype.mapGen = function *mapGen (callback) {
for (var i=0; i<this.length; i++) {
yield callback(this[i])
}
};
We can probably call it like this:
function *upper (items) {
yield* items.mapGen(function * (item) {
try {
yield item.toUpperCase()
} catch (e) {
yield 'null'
}
})
}
Notice that you need to yield twice. That's because the inner yield returns to mapGen then mapGen will yield that value then you need to yield it in order to return that value from upper.
OK. This sort of works but not quite:
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value); // returns generator object
Not exactly what we want. But it sort of makes sense since the first yield returns a yield. So we process each yield as a generator object? Lets see:
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value.next().value.next().value); // works
console.log(u.next().value.next().value.next().value); // doesn't work
OK. Let's figure out why the second call doesn't work.
The upper function:
function *upper (items) {
yield* items.mapGen(/*...*/);
}
yields the return value of mapGen(). For now, let's ignore what mapGen does and just think about what yield actually means.
So the first time we call .next() the function is paused here:
function *upper (items) {
yield* items.mapGen(/*...*/); // <----- yields value and paused
}
which is the first console.log(). The second time we call .next() the function call continue at the line after the yield:
function *upper (items) {
yield* items.mapGen(/*...*/);
// <----- function call resumes here
}
which returns (not yield since there's no yield keyword on that line) nothing (undefined).
This is why the second console.log() fails: the *upper() function has run out of objects to yield. Indeed, it only ever yields once so it has only one object to yield - it is a generator that generates only one value.
OK. So we can do it like this:
var u = upper(['aaa','bbb','ccc']);
var uu = u.next().value; // the only value that upper will ever return
console.log(uu.next().value.next().value); // works
console.log(uu.next().value.next().value); // works
console.log(uu.next().value.next().value); // works
Yay! But, if this is the case, how can the innermost yield in the callback work?
Well, if you think carefully you'll realize that the innermost yield in the callback also behaves like the yield in *upper() - it will only ever return one value. But we never use it more than once. That's because the second time we call uu.next() we're not returning the same callback but another callback which in turn will also ever return only one value.
So it works. Or it can be made to work. But it's kind of stupid.
Conclusion:
After all this, the key point to realize about why yield doesn't work the way we expected is that yield pauses code execution and resumes execution on the next line. If there are no more yields then the generator terminates (is .done).
Second point to realize is that callbacks and all those Array methods (.map, .forEach etc.) aren't magical. They're just javascript functions. As such it's a bit of a mistake to think of them as control structures like for or while.
Epilogue
There is a way to make mapGen work cleanly:
function upper (items) {
return items.mapGen(function (item) {
try {
return item.toUpperCase()
} catch (e) {
return 'null'
}
})
}
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value);
console.log(u.next().value);
console.log(u.next().value);
But you'll notice that in this case we return form the callback (not yield) and we also return form upper. So this case devolves back into a yield inside a for loop which isn't what we're discussing.
You can use another method by "co - npm": co.wrap(fn*)
function doSomething(){
return new promise()
}
var fn = co.wrap(function* (arr) {
var data = yield arr.map((val) => {
return doSomething();
});
return data;
});
fn(arr).then(function (val) {
consloe.log(val)
});