Note: poll will be executed without delay the first time, which is different from the native setInterval.
Q: Why is poll based on setTimeout not setInterval?
A: Please see Execute the setInterval function without delay the first time.
Implementation:
// Promisify setTimeout
const pause = (ms, cb, ...args) =>
  new Promise((resolve, reject) => {
    setTimeout(async () => {
      try {
        resolve(await cb?.(...args))
      } catch (error) {
        reject(error)
      }
    }, ms)
  })
// Promisify setInterval
const poll = async (interval, times, cb, ...args) => {
  let result
  const resolve = value => (times = 0) || (result = value)
  const reject = reason => (times = 0) || (result = Promise.reject(reason))
  await (async function basePoll() {
    if (times > 0) {
      const _result = await cb(...args, resolve, reject)
      if (times) {
        result = _result
        --times && (await pause(interval, basePoll))
      }
    }
  })()
  return result
}
Tests:
import ordinal from 'ordinal'
// Test 1
poll(1000, 3, (a, b, c) => [a, b, c], 1, 2, 3).then(value => console.log(value))
// Test 2
let times = 0
poll(1000, 5, resolve => {
  console.log(`${ordinal(++times)} time`)
  times === 3 && resolve('resolved')
}).then(value => console.log(value))
// Test 3
let times = 0
poll(1000, 5, (resolve, reject) => {
  console.log(`${ordinal(++times)} time`)
  times === 3 && reject('rejected')
}).catch(error => console.error(error))