How to return a Promise with setInterval()
Asked Answered
R

6

6

I am trying to return a Promise object ever 1000ms, but i am not sure how to access the data returned in the Promise when it is inside a setInterval() callback.

EDIT I appears i was not being very clear as to my intentions, so i will try and explain what it is i am trying to do. I making a count down where by the necessary calculations are are don every 1000ms based on specified end date.

Here is the code that provides the return value i would like returned as a Pormise value every 1000ms:

calculateTimeRemaining(endDate: string) {
            const { secondsInDay, daysOfYear, secondsInHour, secondsInMinute } = this.unitsOfTime;
            let distance: number =
                (Date.parse(new Date(endDate).toString()) - Date.parse(new Date().toString())) / this.increment;

            if (distance > 0) {
                // Years left
                if (distance >= daysOfYear * secondsInDay) {
                    // 365.25 * 24 * 60 * 60
                    this.timeRemaining.years = Math.floor(distance / (daysOfYear * secondsInDay));
                    distance -= this.timeRemaining.years * daysOfYear * secondsInDay;
                }
                // Days left
                if (distance >= secondsInDay) {
                    // 24 * 60 * 60
                    this.timeRemaining.days = Math.floor(distance / secondsInDay);
                    distance -= this.timeRemaining.days * secondsInDay;
                }
                // Hours left
                if (distance >= secondsInHour) {
                    // 60 * 60
                    this.timeRemaining.hours = Math.floor(distance / secondsInHour);
                    distance -= this.timeRemaining.hours * secondsInHour;
                }
                // Minutes left
                if (distance >= secondsInMinute) {
                    // 60
                    this.timeRemaining.minutes = Math.floor(distance / secondsInMinute);
                    distance -= this.timeRemaining.minutes * secondsInMinute;
                }
                // Seconds left
                this.timeRemaining.seconds = distance;
            }
            return this.timeRemaining;
        }

Example:

    const interval = window.setInterval(() => {
        return new Promise((resolve, reject) => {
            resolve('Hello');
        });
    }, 1000);

How to i access the Promise object with .then() afterwards?

Does not work:

interval.then((data) => console.log(data);
Reuter answered 19/4, 2019 at 9:31 Comment(6)
A promise is only solved once, so what you want to achieve isn't clearChasseur
I think what you're looking for is an async iterableReamy
you should not return and just run your promise inside the setIntervalDrumfish
setInterval returns the interval reference, it does not return the callback's return. Besides, a promise can only be resolved once. What are you trying to accomplish?Budde
You can only return once, not "every 1000 ms"Sustenance
Updated my questionReuter
S
8

What you are looking for is an Observable, not a Promise. With promises, the callback you pass to then is executed at most once, so this:

interval.then((data) => console.log(data));

...will never print "Hello" more than once, even if you corrected the following mistakes in your code:

  • Whatever you return in a setInterval callback function is ignored.
  • setInterval does not return a promise, but an integer number, uniquely identifying the interval timer that was created.

On the other hand, an Observable can emit multiple events, contrary to a Promise.

There is an Observable proposal for EcmaScript, but you could create your own -- very simplified -- version of it:

class Observable {
    constructor(exec) {
        this.listeners = new Set;
        exec({
            next: (value) => this.listeners.forEach(({next}) => next && next(value)),
            error: (err) => this.listeners.forEach(({error}) => error && error(err)),
            complete: () => this.listeners.forEach(({complete}) => complete && complete())
        });
    }
    subscribe(listeners) {
        this.listeners.add(listeners);
        return { unsubscribe: () => this.listeners.delete(listeners) }
    }
}

// Create an Observable instead of a Promise;
const interval = new Observable(({next}) => {
    setInterval(() => next("Hello"), 1000);
});

// Subscribe to that Observable
const subscription = interval.subscribe({ next: (data) => console.log(data) });

// Optionally use the returned subscription object to stop listening:
document.querySelector("button").addEventListener("click", subscription.unsubscribe);
<button>Stop listening</button>

Note that several JavaScript frameworks have an implementation of Observable.

Schroeder answered 19/4, 2019 at 10:17 Comment(4)
how could i stop looping without using the click event? within resolve or reject of interval.subscribe? interval.subscribe ({next: (data) => this.APISaleID (data) .then (response => { interval.subscribe.unsubscribe = true // stop then here })})Midis
If you have a new question please use the Ask Question button.Schroeder
is not a new question but a question about the sameMidis
There is no click event in this question, and the OP has not asked anything about stopping a loop. It was just an optional addition I added -- which I probably should not have done. So yes, you are asking a new question. Comments are for clarification, correction, remarks, ... not for new questions or even follow-up questions.Schroeder
R
5

Depending on what you're actually trying to do, an async iterable might do the job.

The difference is that an async iterable will only generate the next promise if you consume the last one. Intervals in JavaScript are tricky, even without promises. They try to run their callback at regular intervals, but the execution of any callback may be delayed if the interpreter is busy. That delay will not propagate, though. Also, short intervals will be throttled for background tabs.

Assuming your code is always waiting to consume the async iterable (e.g. in a for…of loop), you could do this:

function delay(t) {
  return new Promise(resolve => setTimeout(resolve, t))
}

async function *interval(t) {
  while(true) {
    let now = Date.now()
    yield "hello"
    await delay(now - Date.now() + t)
  }
}

for await(const greeting of interval(1000)) console.log(greeting)
Reamy answered 19/4, 2019 at 9:50 Comment(1)
I updated my question, hope it makes a litt more senseReuter
P
4

For interval you can define the interval function like this

function interval() {
  return new Promise(function(resolve, reject) {
    setInterval(function() {                 
      resolve('Hello');
    }, 1000)
  })
};

For interval you can use this:

way 1:

interval().then((x) => {
  console.log(x);
})

way 2:

const intervalId = setInterval(() => {
interval().then((x) => {
   console.log(x);
 }, 1000)
})

This is just for stop the interval function after some time. you must clear the interval if you does not need it more.

setTimeout(() => {
  clearInterval(intervalId);
}, 10000);
Prudential answered 19/4, 2019 at 10:40 Comment(1)
Sorry but the const intervalId = setInterval(() => { interval().then((x) => { console.log(x); only returne once, i will have to think of a different methodReuter
M
1

I'm not sure if this will help but; Any function can be made into a promise and the alternative syntax [async keyword] might be useful for you in this case.

async function test() {
      return "hello";
}

test().then( returned => console.log(returned)) // logs hello

setInterval() however does not return a return value rather it returns a "handle".

handle = window . setInterval( handler [, timeout [, arguments ] ] )

... https://www.w3.org/TR/2011/WD-html5-author-20110705/spec.html#timers

You can, however, make promises from an setinterval ...

interval = window.setInterval(makepromise,1000)
async function makepromise() {
    console.log("hello");
}

// or

interval = window.setInterval(async function () {console.log("hello");},1000)

But there is no place for a then then. We are back to callbacks, which we were trying to avoid! But there is functionality perhaps that we can use await within this function.

Better to make your calculateTimeRemaining to a promise And then you can use the then on the interval.

interval = window.setInterval(gameloop,1000);

    function gameloop(endDate: string) {
        calculateTimeRemaining(endDate: string).then(
//
// my then code goes here.
//
        )
    }

async calculateTimeRemaining(endDate: string) {
            const { secondsInDay, daysOfYear, secondsInHour, secondsInMinute } = this.unitsOfTime;
            let distance: number =
                (Date.parse(new Date(endDate).toString()) - Date.parse(new Date().toString())) / this.increment;

            if (distance > 0) {
                // Years left
                if (distance >= daysOfYear * secondsInDay) {
                    // 365.25 * 24 * 60 * 60
                    this.timeRemaining.years = Math.floor(distance / (daysOfYear * secondsInDay));
                    distance -= this.timeRemaining.years * daysOfYear * secondsInDay;
                }
                // Days left
                if (distance >= secondsInDay) {
                    // 24 * 60 * 60
                    this.timeRemaining.days = Math.floor(distance / secondsInDay);
                    distance -= this.timeRemaining.days * secondsInDay;
                }
                // Hours left
                if (distance >= secondsInHour) {
                    // 60 * 60
                    this.timeRemaining.hours = Math.floor(distance / secondsInHour);
                    distance -= this.timeRemaining.hours * secondsInHour;
                }
                // Minutes left
                if (distance >= secondsInMinute) {
                    // 60
                    this.timeRemaining.minutes = Math.floor(distance / secondsInMinute);
                    distance -= this.timeRemaining.minutes * secondsInMinute;
                }
                // Seconds left
                this.timeRemaining.seconds = distance;
            }
            return this.timeRemaining;
        }

However, the value of promises is to avoid callback hell with an excessively complex call back scheme ... where the code is calling back, from call backs, from call backs, etc, etc, etc.

Promises do not operate in a 2nd operating system thread like a webworker. So unless you are attempting to clean up callbacks to make code readable or are actually waiting for something there is no benefit to make use of promises.

setInterval is a clean callback. The Gameloop example is not easier to read and understand because a promise was used. I would suggest in this case it is harder to read. at this point ... unless there are other awaits within the loop or a series of promises that do not need to run synchronously;

Mound answered 19/4, 2019 at 15:2 Comment(0)
A
0

As already mentioned in the comments that you can not return promises on intervals, but you can keep them in a global object and use later,

const jobs = []
const interval = setInterval(() => {
	if(jobs.length == 10) {
		clearInterval(interval);
	}
	let job = Promise.resolve('new job created');
	jobs.push(job);
	console.log('job created')
}, 1000);

setTimeout(() => {
	Promise.all(jobs).then(data => console.log(data))
}, 1000*15);
Abhor answered 19/4, 2019 at 9:51 Comment(2)
Updated my questionReuter
after so many comments your updated question again says i would like returned as a Promise value every 1000ms: which you cannot, how and where you are trying to consume these [email protected]Abhor
D
-3

setInterval already return an integer which is useful to cancel this interval, using clearInterval.

const promise = new Promise((resolve, reject) => {
        resolve('Hello');
    });

Then use it like


promise.then((result) => {
 console.log(result) // Says 'Hello' and will not resolve another value if we call it as it has already been resolved
})

Maybe this is what you tried to achieve. If you want to call it with an interval of 1000 ms.

const getPromiseInstance = () => new Promise((resolve, reject) => {
        resolve(Math.random());
    });

setInterval(() => {
    getPromiseInstance().then((result) => {
      console.log(result)
    })
}, 1000)

You should take a look to Observable, maybe it will fit your needs

Drumfish answered 19/4, 2019 at 9:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.