scheduler.ts 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { ErrorCodes, callWithErrorHandling } from './errorHandling'
  2. import { isArray } from '@vue/shared'
  3. const queue: Function[] = []
  4. const postFlushCbs: Function[] = []
  5. const p = Promise.resolve()
  6. let isFlushing = false
  7. let isFlushPending = false
  8. const RECURSION_LIMIT = 100
  9. type CountMap = Map<Function, number>
  10. export function nextTick(fn?: () => void): Promise<void> {
  11. return fn ? p.then(fn) : p
  12. }
  13. export function queueJob(job: () => void) {
  14. if (!queue.includes(job)) {
  15. queue.push(job)
  16. queueFlush()
  17. }
  18. }
  19. export function queuePostFlushCb(cb: Function | Function[]) {
  20. if (!isArray(cb)) {
  21. postFlushCbs.push(cb)
  22. } else {
  23. postFlushCbs.push(...cb)
  24. }
  25. queueFlush()
  26. }
  27. function queueFlush() {
  28. if (!isFlushing && !isFlushPending) {
  29. isFlushPending = true
  30. nextTick(flushJobs)
  31. }
  32. }
  33. const dedupe = (cbs: Function[]): Function[] => [...new Set(cbs)]
  34. export function flushPostFlushCbs(seen?: CountMap) {
  35. if (postFlushCbs.length) {
  36. const cbs = dedupe(postFlushCbs)
  37. postFlushCbs.length = 0
  38. if (__DEV__) {
  39. seen = seen || new Map()
  40. }
  41. for (let i = 0; i < cbs.length; i++) {
  42. if (__DEV__) {
  43. checkRecursiveUpdates(seen!, cbs[i])
  44. }
  45. cbs[i]()
  46. }
  47. }
  48. }
  49. function flushJobs(seen?: CountMap) {
  50. isFlushPending = false
  51. isFlushing = true
  52. let job
  53. if (__DEV__) {
  54. seen = seen || new Map()
  55. }
  56. while ((job = queue.shift())) {
  57. if (__DEV__) {
  58. checkRecursiveUpdates(seen!, job)
  59. }
  60. callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
  61. }
  62. flushPostFlushCbs(seen)
  63. isFlushing = false
  64. // some postFlushCb queued jobs!
  65. // keep flushing until it drains.
  66. if (queue.length || postFlushCbs.length) {
  67. flushJobs(seen)
  68. }
  69. }
  70. function checkRecursiveUpdates(seen: CountMap, fn: Function) {
  71. if (!seen.has(fn)) {
  72. seen.set(fn, 1)
  73. } else {
  74. const count = seen.get(fn)!
  75. if (count > RECURSION_LIMIT) {
  76. throw new Error(
  77. 'Maximum recursive updates exceeded. ' +
  78. "You may have code that is mutating state in your component's " +
  79. 'render function or updated hook or watcher source function.'
  80. )
  81. } else {
  82. seen.set(fn, count + 1)
  83. }
  84. }
  85. }