Say I have the following generator that produces an infinite range:
const generateInfiniteRange = function* () {
for (let i = 0; true; i++) {
yield i;
}
};
const infiniteRange = generateInfiniteRange();
const infiniteRange$ = from(infiniteRange);
I can do something like this with RxJS:
const predicate = i => i < 10;
infiniteRange$.pipe(
takeWhile(predicate),
);
Now let's say that the predicate is asynchronous:
const predicate = async i => i < 10;
infiniteRange$.pipe(takeWhile(predicate)).subscribe(console.log);
How can I make this code work?
infiniteRange$.pipe(takeWhile(predicate));
I've tried using map as follows:
infiniteRange$.pipe(
map(async i => ({
i,
predicateResult: await predicate(i),
})),
takeWhile(({predicateResult}) => predicateResult),
pluck('i'),
);
but that just ends up mapping everything to a promise that always coerces to a truthy value, so everything passes through the takeWhile
I figured based on a previous question I've asked (for which originally I asked this question as an addendum in a comment of an answer, before deciding a question was more appropriate) that I could use concatMap, but that just generates an infinite emission before any of the internal observables hit the pipe.
If I understand this right, you can not achieve what you want.
The simple reason is that when, with from you create an Observable out of a generator, then you are creating a synchronous Observable, i.e. an Observable which emits all its values synchronously.
You can see clearly this looking at the fromIterable source code
function fromIterable<T>(iterable: Iterable<T>) {
return new Observable((subscriber: Subscriber<T>) => {
for (const value of iterable) {
subscriber.next(value);
if (subscriber.closed) {
return;
}
}
subscriber.complete();
});
}
As you can see, the for loop is exited only when the subscriber is closed. But in our case the subscriber will be closed asynchronously, i.e. we need Node to stop the execution of the loop since no instructions are left and pick the next callback, the one that runs the predicate. This will never happen since the for loop will never end.
So the summary is that you can not have an async predicate working with a synchronous infinite stream of values, which is what you create using a Generator.
By the way, in order for the code to compile, you need to use concatMap to transform the value notified by the source stream to the Object used by the predicate. So a code that compiles is this one
infiniteRange$.pipe(
tap(i => {
console.log(i)
}),
concatMap(async i => {
console.log('concatMap hit');
return {
i,
predicateResult: await predicate(i),
}
}),
takeWhile(({predicateResult}) => {
console.log('takeWhile hit');
return predicateResult
}),
pluck('i'),
).subscribe(console.log);
Running this snippet, you will see that you enter once in the concatMap input function (i.e. "concatMap hit" will be printed once) while you never enter the function passed to takeWhile (i.e. "takeWhile hit" will never be printed).
A SOLUTION WITH ASYNC GENERATORS
Actually, if you change the generator to be async, then using concatMap in the pipe we can reach the result you are looking for.
This is how the code would look like
const generateInfiniteRangeAsync = async function* () {
for (let i = 0; true; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
};
const predicate = async i => i < 10;
const infiniteRangeAsync = generateInfiniteRangeAsync();
const infiniteRangeAsync$ = from(infiniteRangeAsync);
infiniteRangeAsync$.pipe(
concatMap(async i => {
console.log('concatMap hit');
return {
i,
predicateResult: await predicate(i),
}
}),
takeWhile(({predicateResult}) => {
console.log('takeWhile hit');
return predicateResult
}),
pluck('i'),
)
.subscribe(console.log);
A simple solution, using iter-ops, which can handle asynchronous predicates out of the box:
import {pipeAsync, stop} from 'iter-ops';
// your infinite generator:
const generateInfiniteRange = function * () {
for (let i = 0; true; i++) {
yield i;
}
};
// create our iterable:
const i = pipeAsync(generateInfiniteRange(), stop(async a => a >= 10));
// test:
(async function () {
for await(const a of i) {
console.log(a); //=> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
}
})();
P.S. I am the author of the library.
Related
I have an array of objects containing a field duration that specifies the duration that the Object should stay visible.
Each Object of array has it's own duration.
So i want to make each object appear after another with the help of setTimout.
Something like this : (onClick)
const playScenes = () => {
const cpScenes = [...scenes];
for(let i; i < cpScenes.length;i++){
const timer = setTimeout(() => {
showScene(cpScenes[i]); //Show the current scene
}, cpScenes[i].duration); //Time changes automatically
}
clearTimeout(timer);
};
The way am thinking about this is that setTimeout should block execution of for loop and then run after specified time and then the loop continues..
Neither setTimeout nor setInterval block execution. Instead what you can do is wrapping it a promise like:
async function playscenes() {
const cpScenes = [...scenes];
for(let i; i < cpScenes.length;i++){
await (new Promise( (resolve) =>
const timer = setTimeout(() => {
showScene(cpScenes[i]);
clearTimeout(timer);
resolve();
}, cpScenes[i].duration);
})
}
Basically what this does, is for every single loop, the program will wait for the new Promise to resolve, which that happens when the timer has run out.
If you don't understand asynchronous code (async/await) and promises work, I'd highly recommend that you do your research first.
Although this code should not run inside an a React component since i assume that you change the state every time the setTimeout is triggered.
What you could do is:
imports ...
async function playscenes(showScene){...}
let scenesPlaying = false;
export function YourComponent(...){
...
if(!scenesPlaying) {
playscenes(showScene);
scenesPlaying = true;
}
...
}
I don't really see how it is related to react, anyway, you can use recursion instead of for-loop to make it work
function playScenes(scenes) {
if (scenes.length === 0) {
return;
}
const [scene, ...rest] = scenes;
setTimeout(() => {
showScene(scene);
playScenes(rest);
}, scene.duration);
}
Here's a simple for loop based solution:
function newComputerMessage() {
let total = 0;
for (let i = 0; i < cpScenes.length; i++) {
total += cpScenes[i].duration;
setTimeout(() => showScene(cpScenes[i]), total);
}
}
In case you want to see the working, here's a working example snippet with dummy values:
let cpScenes = [5000, 5000, 3000, 4000]
function test() {
let total = 0;
for (let i = 0; i < cpScenes.length; i++) {
total += cpScenes[i];
setTimeout(() => showScene('test'), total);
}
}
function showScene(num) {
console.log(num);
}
test()
I am working on this react component. I want it to modify an array using a loop and reflect the changes in the UI after each iteration. I also added some code to halt the program so that each iteration will be displayed for a short time.
As for the UI, I have the numbers in the array itself followed by a button to start the loop. It seems that after pressing the button, the ui just freezes and the array is displayed but only after the last iteration of the loop. I want the onscreen array to change after every loop iteration. I tried using this.forceUpdate() but it did not change anything. I also tried using the spread (...) operator for changing the state but that did not change anything either. The relevant code and the GitHub are pasted below. The code can be found in src/ChangingArray.js in the project files. Thanks in advance.
import React, { Component } from 'react'
export default class ChangingArray extends Component {
constructor(props) {
super(props);
this.state = {
array: [1, 2, 3]
}
this.modifyArray = this.modifyArray.bind(this);
}
modifyArray = () => {
for (let i = 0; i < 15; i++) {
let newArray = [];
for (let j = 0; j < this.state.array.length; j++) {
newArray[j] = this.state.array[j];
}
newArray[i % newArray.length] = i;
this.setState({
array: [...newArray],
})
this.forceUpdate();
//Code to halt the program
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < 250);
}
}
render() {
return (
<div>
{this.state.array}
<button onClick={this.modifyArray}>modify</button>
</div>
)
}
}
https://github.com/AntonM-248/longestPalindromicSubstringVisualizer
By doing
do {
currentDate = Date.now();
} while (currentDate - date < 250);
you are synchronously blocking the thread, so your UI will obviously freeze:
I'm not sure I grasped what you are trying to achieve, but if you want to block the execution of a loop for a while before going to the next cycle, you need to implement an async iterator, let me show you how:
function makeAsyncArray(arr, delay) {
return {
[Symbol.asyncIterator]() {
let i = 0;
return {
next() {
const done = i === arr.length;
const value = done ? undefined : arr[i];
i++;
return new Promise((res) =>
setTimeout(() => res({ value, done }), delay)
);
},
return() {
// This will be reached if the consumer called 'break' or 'return' early in the loop.
return { done: true };
},
};
},
};
}
This function returns a special object called Async Iterator , this kind of iterators can be iterated by using this syntax:
for await (const el of arr) //do something
Every element returned by this Iterator is a Promise so it can be awaited inside an async function and that's what we need if we want to make delayed loops that wait for something before to proceed to go on with cyclle. In this example, I used just a delay with a setTimeout to yield and block the loop asynchronously for some time, but you can use this logic to resolve that Promise with any other async logic ( for example the result of a fetch request ).
This is a working React implementation of this logic:
const arr = makeAsyncArray([1, 2, 3, 4, 5,6,7,8,9,10], 1000);
export default function App() {
const [data, setData] = useState([]);
// Data array will be populated adding one element every 1000ms since we have initialized the async iterator to resolve after 1000ms
useEffect(() => {
(async () => {
for await (const el of arr) {
setData((d) => [...d, el]);
}
})();
}, []);
return <div>{data}</div>;
}
function makeAsyncArray(arr, delay) {
return {
[Symbol.asyncIterator]() {
let i = 0;
return {
next() {
const done = i === arr.length - 1;
const value = done ? undefined : arr[i];
i++;
return new Promise((res) =>
setTimeout(() => res({ value, done }), delay)
);
},
return() {
// This will be reached if the consumer called 'break' or 'return' early in the loop.
return { done: true };
},
};
},
};
}
The live demo: https://stackblitz.com/edit/react-yie8pg
Note: I cannot use async.
I like to use the reduce pattern in cases where I need to run over an array and execute the same function on its members and return a promise, like so:
function get_count() {
return new Promise(function(resolve, reject) {
resolve(3);
});
}
function recursively_execute(data) {
return new Promise(function(resolve, reject) {
resolve(data);
});
}
function reduce_promise_pattern() {
const get_batch_run_count = get_count();
const batch_process = get_batch_run_count.then((count_value) => {
const run_count = new Array(count_value).fill('batch');
function recursive_function(data) {
console.log('Running batch!');
return recursively_execute(data).then(() => {
return data;
});
}
return run_count.reduce((previous_promise) => {
return previous_promise.then((previous_response) => {
test_data = {
'test': 1
};
return recursive_function(test_data);
})
}, Promise.resolve())
});
return batch_process;
}
This will run 3 times because of the run_count which basically builds an array of 3 items. Although it works, this feels like a hack to me.
This approach works when my list is already pre-defined with unique items and these items, well, individually are used inside that reduce as data that is built upon for example, if I have 3 steps to go through, these 3 steps are all unique and each step's data will be used within that one run...but in my case? I'm just tricking the system to think these are different items.
What is the alternative to this?
You reached the limits of Promise chains, although they work they ain't readable. That's why async / await was introduced to handle exactly these usecases, with them you can just halt all kinds of (nested) loops without having to maintain promises for each:
async function reducePromisePattern() {
for(let i = await getCount(); i >= 0; i--) {
await recursiveFunction({'test': 1 });
}
}
If you can't use / transpile async, you could still write some small helpers to do the looping for you, e.g.:
function loopAsync(times, fn) {
function task() {
times--;
if(times <= 0) return;
return fn().then(task);
}
return Promise.resolve().then(task);
}
function reducePromisePattern() {
return getCount().then(function(count) {
return asyncLoop(count, function() {
return recursiveFunction({ test: 1 });
});
});
}
Here are two options without nesting functions in one another. The first one simply uses a for-loop while the second function uses a recursive solution. The last argument of both solutions is optional and should only be used if you want to pass the return data forward from one run to the next (similar to reduce).
const sleep = () => new Promise(resolve => setTimeout(resolve, Math.random() * 1500 + 500));
// solution #1 - for-loop
function times1(n, callback, init) {
var promise = Promise.resolve(init);
for (; n > 0; --n) {
promise = promise.then(val => callback(val));
}
return promise;
}
// example usage
times1(3, n => {
console.log("solution #1 -", n);
return sleep().then(() => n + 1);
}, 0);
// solution #2 - recursive
function times2(n, callback, init) {
var promise = Promise.resolve(init);
if (n <= 0) return promise;
return promise.then(val => times2(n - 1, callback, callback(val)));
}
// example usage
times2(3, n => {
console.log("solution #2 -", n);
return sleep().then(() => n + 1);
}, 0);
As far as I know, async/await is just syntactic sugar over promise.then. Consider this code snippet:
function sleep(n){
return new Promise(res => setTimeout(res, n));
}
function* range(n){
var i = 0;
while(i < n) yield i++;
}
async function doStuff(){
for(let n of range(10)){
console.log(n); // print the number
await sleep(1000); // wait for 1 second
}
}
async/await makes the code very linear, efficient and easy to understand. One thing to keep in mind is that range does not have to have an actual end for this to work.
The problem now is how this can be rewritten using pre-ES7 era's promise.then. Here's a possible implementation of the same loop:
function doStuff(){
return Array.from(range(10)).reduce((acc, ele) => {
return acc
.then(() => console.log(ele))
.then(() => sleep(1000))
}, Promise.resolve());
}
Ignoring the fact that the code isn't quite elegant, the use of Array.from(range(10))
creates an extra array that isn't needed, and
assumes range(10) will end some point in the future.
Doesn't look like a good conversion.
We can also completely reinvent the wheel by using yield as await, but that would make the syntax non ES5-compliant. The goal here is to:
Rewrite using ES5-compliant syntax
Use the promise-returning sleep function
Dynamically chain the sleep promise while allowing the iterator to not have an end
doStuff can be chained:
doStuff().finally(cleanUp); // clean up if something failed
(Optional) Code should not be overly complex
Any idea?
I think the following may do the trick, your example doesn't show what to do with resolve value and how it relates to the iterator values so I made a change to how sleep is called.
Some promise polyfils may run out of stack space with high promise chains so you should check your polyfil (if its implementation returns and continues with a setTimeout the stack should clear but some polyfils may not implement it this way).
function sleep(n){
return new Promise(res => setTimeout(_=>res(n/100), n));
}
function* range(n){
var i = 0;
while(i < n) yield i++;
}
function doStuff(){
const processValue =
resolve => {
console.log("resolved with:",resolve);
// if(resolve===3){throw "nope";}
return sleep(resolve*100);
},
rec = (p,iter) => {
const result = iter.next();
if (result.done){
return p;
}
p = p.then(_=>processValue(result.value))
return p.then(
resolve=>{
return rec(p,iter)
}
);
},
iter = range(10),
firstResult = iter.next();
if(firstResult.done){
return processValue(firstResult.value);
}
return rec(processValue(firstResult.value),iter);
}
doStuff()
.then(
x=>console.log("done:",x)
,reject=>console.warn("fail:",reject)
);
I've always said that if you need an asynchronous design pattern first look at the async library. In this case, since you're using promises, take a look at the promisified async-q library. The translation is straight forward:
var n = 0;
async.whilst(() => n < 10, () => {
n++;
console.log(n);
return sleep(1000);
})
I am trying to implement a while loop using promises.
The method outlined here seems to work.
http://blog.victorquinn.com/javascript-promise-while-loop
it uses a function like this
var Promise = require('bluebird');
var promiseWhile = function(condition, action) {
var resolver = Promise.defer();
var loop = function() {
if (!condition()) return resolver.resolve();
return Promise.cast(action())
.then(loop)
.catch(resolver.reject);
};
process.nextTick(loop);
return resolver.promise;
};
This seems to use anti-patterns and deprecated methods like cast and defer.
Does anyone know a better or more modern way to accomplish this?
Thanks
cast can be translated to resolve. defer should indeed not be used.
You'd create your loop only by chaining and nesting then invocations onto an initial Promise.resolve(undefined).
function promiseWhile(predicate, action, value) {
return Promise.resolve(value).then(predicate).then(function(condition) {
if (condition)
return promiseWhile(predicate, action, action());
});
}
Here, both predicate and action may return promises. For similar implementations also have a look at Correct way to write loops for promise. Closer to your original function would be
function promiseWhile(predicate, action) {
function loop() {
if (!predicate()) return;
return Promise.resolve(action()).then(loop);
}
return Promise.resolve().then(loop);
}
I prefer this implementation as its easier to simulate break and continue with it:
var Continue = {}; // empty object serves as unique value
var again = _ => Continue;
var repeat = fn => Promise.try(fn, again)
.then(val => val === Continue && repeat(fn) || val);
Example 1: stops when either the source or the destination indicate an error
repeat(again =>
source.read()
.then(data => destination.write(data))
.then(again)
Example 2: stop randomly if the coin flip given 90% probability results with a 0
var blah = repeat(again =>
Promise.delay(1000)
.then(_ => console.log("Hello"))
.then(_ => flipCoin(0.9) && again() || "blah"));
Example 3: Loop with condition that returns the sum:
repeat(again => {
if (sum < 100)
return fetchValue()
.then(val => sum += val)
.then(again));
else return sum;
})