1# `setInterval`<wbr>`Async`<br>[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT) [![npm version](https://img.shields.io/npm/v/set-interval-async.svg?style=flat-square)](https://badge.fury.io/js/set-interval-async) [![Build Status](https://img.shields.io/travis/ealmansi/set-interval-async.svg?style=flat-square)](https://travis-ci.org/ealmansi/set-interval-async) [![Coverage Status](https://img.shields.io/coveralls/github/ealmansi/set-interval-async.svg?style=flat-square)](https://coveralls.io/github/ealmansi/set-interval-async?branch=master) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 2 3Modern version of [setInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval) for promises and async functions available in Node.js and browsers. 4 5`setIntervalAsync` works both on Node.js and in the browser, providing the same <br> 6familiar interface as `setInterval` for asynchronous functions, while preventing<br> 7multiple executions from overlapping in time. 8 9 10[![NPM](https://nodei.co/npm/set-interval-async.png)](https://nodei.co/npm/set-interval-async/) [![JavaScript Style Guide](https://cdn.rawgit.com/standard/standard/master/badge.svg)](https://github.com/standard/standard) 11 12# Getting Started 13 14## Node.js 15 16You can install `setIntervalAsync` using npm: 17 18```bash 19npm install -E set-interval-async 20``` 21 22Or using Yarn: 23 24```bash 25yarn add -E set-interval-async 26``` 27 28Now, you can require `setIntervalAsync` in CommonJS: 29 30```javascript 31// Choose one of the following flavors: dynamic, fixed, legacy. 32 33const { setIntervalAsync } = require('set-interval-async/dynamic') 34const { setIntervalAsync } = require('set-interval-async/fixed') 35const { setIntervalAsync } = require('set-interval-async/legacy') 36const { clearIntervalAsync } = require('set-interval-async') 37 38// Or require all at once: 39 40const { 41 dynamic: { setIntervalAsync: setIntervalAsyncD }, 42 fixed: { setIntervalAsync: setIntervalAsyncF }, 43 legacy: { setIntervalAsync: setIntervalAsyncL }, 44 clearIntervalAsync 45} = require('set-interval-async') 46``` 47 48Or else, you can use ES6 modules syntax: 49 50```javascript 51// Choose one of the following flavors: dynamic, fixed, legacy. 52 53import { setIntervalAsync } from 'set-interval-async/dynamic' 54import { setIntervalAsync } from 'set-interval-async/fixed' 55import { setIntervalAsync } from 'set-interval-async/legacy' 56import { clearIntervalAsync } from 'set-interval-async' 57 58// Import all at once: 59 60import { 61 dynamic, 62 fixed, 63 legacy, 64 clearIntervalAsync 65} from 'set-interval-async' 66const { setIntervalAsync: setIntervalAsyncD } = dynamic 67const { setIntervalAsync: setIntervalAsyncF } = fixed 68const { setIntervalAsync: setIntervalAsyncL } = legacy 69 70``` 71 72## Browser 73 74In the browser, you can add a script tag in your HTML: 75 76```html 77<script src="https://unpkg.com/set-interval-async"></script> 78``` 79 80After the script is loaded, a module `SetIntervalAsync` will be defined in the global context. 81Now, you can retrieve the `setIntervalAsync` function in any of its flavors: 82 83```javascript 84// Choose one of the following flavors: dynamic, fixed, legacy. 85 86var setIntervalAsync = SetIntervalAsync.dynamic.setIntervalAsync 87var setIntervalAsync = SetIntervalAsync.fixed.setIntervalAsync 88var setIntervalAsync = SetIntervalAsync.legacy.setIntervalAsync 89 90// Load `clearIntervalAsync` as well. 91 92var clearIntervalAsync = SetIntervalAsync.clearIntervalAsync 93``` 94 95# Usage 96 97In the most basic scenario, you can use `setIntervalAsync` the same way you would use vanilla `setInterval`. For example, the following code: 98 99```javascript 100const { 101 setIntervalAsync, 102 clearIntervalAsync 103} = require('set-interval-async/dynamic') 104 105setIntervalAsync( 106 () => console.log('Hello'), 107 1000 108) 109``` 110 111will print 'Hello' to the console once every second. However, you can also provide an async function (or a function returning a promise): 112 113```javascript 114const timer = setIntervalAsync( 115 async () => { 116 console.log('Hello') 117 await doSomeWork() 118 console.log('Bye') 119 }, 120 1000 121) 122 123// or equivalently 124 125const timer = setIntervalAsync( 126 () => { 127 console.log('Hello') 128 return doSomeWork().then( 129 () => console.log('Bye') 130 ) 131 }, 132 1000 133) 134``` 135 136which has the added nicety that now you can wait until the cycle is fully stopped before moving on by using `clearIntervalId`. This is particularly useful when, at the end of a unit test, you want to make sure that no asynchronous code continues running by the time your test manager moves on to the next one. 137 138```javascript 139it('should test something', async () => { 140 const timer = setIntervalAsync( 141 /* some async function */, 142 /* some interval */ 143 ) 144 // Do some assertions. 145 await clearIntervalAsync(timer) 146 // At this point, all timers have been cleared, and the last 147 // execution is guaranteed to have finished as well. 148}) 149``` 150 151Where `setIntervalAsync` really shines is in those situations where the given asynchronous function might take longer to compute than the configured interval and, at the same time, is not safe to execute more than once at a time. Using vanilla `setInterval` will break your code in this scenario, whereas `setIntervalAsync` guarantees that the function will never execute more than once at the same time. For example, consider: 152 153```javascript 154async function processQueue (queue) { 155 if (queue.length === 0) { 156 return 157 } 158 let head = queue[0] 159 await doSomeWork(head) 160 queue.shift() // Removes the first element. 161} 162``` 163 164The function above should never get called again before the previous execution is completed. Otherwise, the queue's first element will get processed twice, and the second element will be skipped. However, with `setIntervalAsync`, the following is perfectly safe: 165 166```javascript 167setIntervalAsync(processQueue, 1000, queue) 168``` 169 170since `setIntervalAsync` will guarantee that the function is never executed more than once at any given moment. You can choose whether you wish to use the `Dynamic` or `Fixed` flavors, which will either launch every execution as soon as possible or set a fixed delay between the end of one execution and the start of the next one. See [Dynamic and Fixed `setIntervalAsync`](#dynamic-and-fixed-setintervalasync) for more details. 171 172To see a full set of examples and instructions on how to run them, check out our [examples](https://github.com/ealmansi/set-interval-async/tree/master/examples) directory. 173 174# Motivation 175 176If you've ever had to deal with weird, subtle bugs as a consequence of using `setInterval`[1] on asynchronous functions, or had to manually reimplement `setInterval` using `setTimeout`[2] to prevent multiple executions of the same asynchronous function from overlapping, then this library is a drop-in replacement that will solve your issues. 177 178`setInterval` runs a given function repeateadly, once every fixed number of milliseconds. This may cause problems whenever the function takes longer to execute than the given interval, since it will be called again before the first execution has finished. This is often a problem for non-reentrant functions; ie. functions that are not designed to allow multiple executions at the same time. 179 180`setIntervalAsync` is a drop-in replacement of `setInterval` which shares the same API but is safe to use with non-reentrant, asynchronous functions. 181 182[1] https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout<br> 183[2] https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval 184 185# Dynamic and Fixed `setIntervalAsync` 186 187`setIntervalAsync` provides two strategies which can be used to prevent a recurring function from executing more than once at any given moment: 188 189- **Dynamic**: If possible, the given function is called once every `interval` milliseconds. If any execution takes longer than the desired interval, the next execution is delayed until the previous one has finished, and called immediately after this condition is reached.<br><br>![Dynamic setIntervalAsync diagram.](https://github.com/ealmansi/set-interval-async/raw/master/assets/dynamic.png) 190 191- **Fixed**: The given function is called repeatedly, guaranteeing a fixed delay of `interval` milliseconds between the end of one execution and the start of the following one.<br><br>![Fixed setIntervalAsync diagram.](https://github.com/ealmansi/set-interval-async/raw/master/assets/fixed.png) 192 193You can choose whichever strategy works best for your application. When in doubt, the `Dynamic` strategy will likely suffice for most use cases, keeping the interval as close as possible to the desired one. 194 195# Documentation 196 197You can browse the full API in our [Documentation](https://emilio.almansi.me/set-interval-async/) page. 198 199# Contributing 200 201In order to contribute to this project, you will need to first clone the repository: 202 203```bash 204git clone https://github.com/ealmansi/set-interval-async.git 205``` 206 207Make sure that [Yarn](https://yarnpkg.com/en/) is installed globally on your system, 208install all project dependencies, and build the project: 209 210```bash 211yarn 212yarn build 213``` 214 215Now, you can run the tests and make sure that everything is up and running correctly: 216 217```bash 218yarn test 219``` 220 221If the previous step succeeds, you're ready to start developing on this project. <br>Pull requests are welcome! 222 223You can verify that your code follows the [JavaScript Standard Style](https://standardjs.com/) with the following command: 224 225```bash 226yarn lint 227``` 228 229# License 230 231[MIT](https://raw.githubusercontent.com/ealmansi/set-interval-async/master/LICENSE) 232