手把手实现 JS 的 Promise 轮训机制
MIT License
https://github.com/Haixiang6123/my-promise-poller
setInterval
setInterval
promise-poller
setInterval
Low
const promisePoller = (taskFn: Function, interval: number) => {
setInterval(() => {
taskFn();
}, interval)
}
so easy
setInterval
setTimeout()setInterval() setTimeout
so easy
interface Options {
taskFn: Function
interval: number
}
const promisePoller = (options: Options) => {
const {taskFn, interval} = options
const poll = () => {
setTimeout(() => {
taskFn()
poll()
}, interval)
}
poll()
}
Options
setTimeout
setTimeout
delay
delay poll
export const delay = (interval: number) => new Promise(resolve => {
setTimeout(resolve, interval)
})
const promisePoller = (options: Options) => {
const {taskFn, interval} = options
const poll = () => {
taskFn()
delay(interval).then(poll) // delay setTimeout
}
poll()
}
"promise" promisePoller
Promise promisify
Promise
const promisePoller = (options: Options) => {
const {taskFn, interval, masterTimeout} = options
return new Promise((resolve, reject) => { // Promise
const poll = () => {
const result = taskFn()
delay(interval).then(poll)
}
poll()
})
}
reject resolve reject resolve
reject timeout masterTimeout
Options
setTimeout
interface Options {
taskFn: Function
interval: number
masterTimeout?: number // timeout
}
const promisePoller = (options: Options) => {
const {taskFn, interval, masterTimeout} = options
let timeoutId
return new Promise((resolve, reject) => {
if (masterTimeout) {
timeoutId = setTimeout(() => {
reject('Master timeout') //
}, masterTimeout)
}
const poll = () => {
taskFn()
delay(interval).then(poll)
}
poll()
})
}
resolve shouldContinue
interface Options {
taskFn: Function
interval: number
shouldContinue: (err: string | null, result: any) => boolean //
masterTimeout?: number
}
const promisePoller = (options: Options) => {
const {taskFn, interval, masterTimeout, shouldContinue} = options
let timeoutId: null | number
return new Promise((resolve, reject) => {
if (masterTimeout) {
timeoutId = window.setTimeout(() => {
reject('Master timeout') //
}, masterTimeout)
}
const poll = () => {
const result = taskFn()
if (shouldContinue(null, result)) {
delay(interval).then(poll) //
} else {
if (timeoutId !== null) { // timeoutId
clearTimeout(timeoutId)
}
resolve(result) // taskFn
}
}
poll()
})
}
Promisify poller
masterTimeout
timeout
Options
taskTimeout
taskFn
timeout taskFn
taskFn
promisify promise timeout
interface Options {
taskFn: Function
interval: number
shouldContinue: (err: string | null, result: any) => boolean
masterTimeout?: number
taskTimeout?: number // timeout
}
// promise
const timeout = (promise: Promise<any>, interval: number) => {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => reject('Task timeout'), interval)
promise.then(result => {
clearTimeout(timeoutId)
resolve(result)
})
})
}
const promisePoller = (options: Options) => {
const {taskFn, interval, masterTimeout, taskTimeout, shouldContinue} = options
let timeoutId: null | number
return new Promise((resolve, reject) => {
if (masterTimeout) {
timeoutId = window.setTimeout(() => {
reject('Master timeout')
}, masterTimeout)
}
const poll = () => {
let taskPromise = Promise.resolve(taskFn()) // promisify
if (taskTimeout) {
taskPromise = timeout(taskPromise, taskTimeout) //
}
taskPromise
.then(result => {
if (shouldContinue(null, result)) {
delay(interval).then(poll)
} else {
if (timeoutId !== null) {
clearTimeout(timeoutId)
}
resolve(result)
}
})
.catch(error => {
})
}
poll()
})
}
taskFn
promisifytimeout
taskFn
timeouttaskFn
reject taskPromise
catchtimeout
reject
Options
retries
interface Options {
taskFn: Function
interval: number
shouldContinue: (err: string | null, result?: any) => boolean
masterTimeout?: number
taskTimeout?: number
retries?: number //
}
catch retries
0 shouldContinue
true
true
const promisePoller = (options: Options) => {
...
let rejections: Array<Error | string> = []
let retriesRemain = retries
return new Promise((resolve, reject) => {
...
const poll = () => {
...
taskPromise
.then(result => {
...
})
.catch(error => {
rejections.push(error) // rejections
if (--retriesRemain === 0 || !shouldContinue(error)) { //
reject(rejections) //
} else {
delay(interval).then(poll); //
}
})
}
poll()
})
}
rejections
error 10 2 2
1/3 ... 2/3 ... 3/3 ... Options
retriesRemain
interface Options {
taskFn: Function
interval: number
shouldContinue: (err: string | null, result?: any) => boolean
progressCallback?: (retriesRemain: number, error: Error) => unknown //
masterTimeout?: number
taskTimeout?: number
retries?: number //
}
const promisePoller = (options: Options) => {
...
let rejections: Array<Error | string> = []
let retriesRemain = retries
return new Promise((resolve, reject) => {
...
const poll = () => {
...
taskPromise
.then(result => {
...
})
.catch(error => {
rejections.push(error) // rejections
if (progressCallback) {
progressCallback(retriesRemain, error) // retriesRemain
}
if (--retriesRemain === 0 || !shouldContinue(error)) { //
reject(rejections) //
} else {
delay(interval).then(poll); //
}
})
}
poll()
})
}
shouldContinue
taskFn
promisePoller
taskFn
return false
reject("CANCEL_TOKEN")
const CANCEL_TOKEN = 'CANCEL_TOKEN'
const promisePoller = (options: Options) => {
const {taskFn, masterTimeout, taskTimeout, progressCallback, shouldContinue, retries = 5} = mergedOptions
let polling = true
let timeoutId: null | number
let rejections: Array<Error | string> = []
let retriesRemain = retries
return new Promise((resolve, reject) => {
if (masterTimeout) {
timeoutId = window.setTimeout(() => {
reject('Master timeout')
polling = false
}, masterTimeout)
}
const poll = () => {
let taskResult = taskFn()
if (taskResult === false) { //
taskResult = Promise.reject(taskResult)
reject(rejections)
polling = false
}
let taskPromise = Promise.resolve(taskResult)
if (taskTimeout) {
taskPromise = timeout(taskPromise, taskTimeout)
}
taskPromise
.then(result => {
...
})
.catch(error => {
if (error === CANCEL_TOKEN) { //
reject(rejections)
polling = false
}
rejections.push(error)
if (progressCallback) {
progressCallback(retriesRemain, error)
}
if (--retriesRemain === 0 || !shouldContinue(error)) {
reject(rejections)
} else if (polling) { // polling true
delay(interval).then(poll);
}
})
}
poll()
})
}
taskFn
false
catch error CANCEL_TOKEN
taskFn
polling
false
reject
polling
true
poll
interval
2 linear-backoff exponential-backoff interval
export const strategies = {
'fixed-interval': {
defaults: {
interval: 1000
},
getNextInterval: function(count: number, options: Options) {
return options.interval;
}
},
'linear-backoff': {
defaults: {
start: 1000,
increment: 1000
},
getNextInterval: function(count: number, options: Options) {
return options.start + options.increment * count;
}
},
'exponential-backoff': {
defaults: {
min: 1000,
max: 30000
},
getNextInterval: function(count: number, options: Options) {
return Math.min(options.max, Math.round(Math.random() * (Math.pow(2, count) * 1000 - options.min) + options.min));
}
}
};
getNextInterval
Options
type StrategyName = 'fixed-interval' | 'linear-backoff' | 'exponential-backoff'
interface Options {
taskFn: Function
shouldContinue: (err: Error | null, result?: any) => boolean //
progressCallback?: (retriesRemain: number, error: Error) => unknown //
strategy?: StrategyName //
masterTimeout?: number
taskTimeout?: number
retries?: number
// fixed-interval
interval?: number
// linear-backoff
start?: number
increment?: number
// exponential-backoff
min?: number
max?: number
}
poll
delay
nextInterval delay(nextInterval)
const promisePoller = (options: Options) => {
const strategy = strategies[options.strategy] || strategies['fixed-interval'] // fixed-interval
const mergedOptions = {...strategy.defaults, ...options} //
const {taskFn, masterTimeout, taskTimeout, progressCallback, shouldContinue, retries = 5} = mergedOptions
let polling = true
let timeoutId: null | number
let rejections: Array<Error | string> = []
let retriesRemain = retries
return new Promise((resolve, reject) => {
if (masterTimeout) {
timeoutId = window.setTimeout(() => {
reject(new Error('Master timeout'))
polling = false
}, masterTimeout)
}
const poll = () => {
let taskResult = taskFn()
if (taskResult === false) {
taskResult = Promise.reject(taskResult)
reject(rejections)
polling = false
}
let taskPromise = Promise.resolve(taskResult)
if (taskTimeout) {
taskPromise = timeout(taskPromise, taskTimeout)
}
taskPromise
.then(result => {
if (shouldContinue(null, result)) {
const nextInterval = strategy.getNextInterval(retriesRemain, mergedOptions) //
delay(nextInterval).then(poll)
} else {
if (timeoutId !== null) {
clearTimeout(timeoutId)
}
resolve(result)
}
})
.catch((error: Error) => {
if (error.message === CANCEL_TOKEN) {
reject(rejections)
polling = false
}
rejections.push(error)
if (progressCallback) {
progressCallback(retriesRemain, error)
}
if (--retriesRemain === 0 || !shouldContinue(error)) {
reject(rejections)
} else if (polling) {
const nextInterval = strategy.getNextInterval(retriesRemain, options) //
delay(nextInterval).then(poll);
}
})
}
poll()
})
}
promisePoller
npm promise-poller