Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Vue Task Scheduling and Batch Updates

Notes 1

Purpose

Vue's task scheduling system enables batch updates or asynchronous execution of reactive effects. When reactive data changes trigger side effect functions to re-execute, the system provides control over when, how many times, and in what manner these effects execute.

Example

const appState = reactive({
  counter: 1
})

effect(() => {
  console.log('counter value:', appState.counter)
})

appState.counter++

console.log('execution finished')

Implementing Schedulable Effects

const appState = reactive({
  counter: 1
})

effect(() => {
  console.log(appState.counter)
}, {
  // When counter changes, the scheduler function executes instead
  // This causes 'execution finished' to run first due to setTimeout wrapping
  scheduler (task) {
    // Execute asynchronously
    setTimeout(() => {
      task()
    }, 0)
  }
})

appState.counter++

console.log('execution finished')

The scheduler controls when side effects execute. Previously, console.log(appState.counter) would execute immediately after appState.counter++. With the scheduler, changes trigger the scheduler logic instead.

Batch Updates and Asynchronous Execution

Consider this code - how many times will the counter print? 100 or 101?

const appState = reactive({
  counter: 1
})

effect(() => {
  console.log('counter:', appState.counter)
})

let iterations = 100

while (iterations--) {
  appState.counter++
}

For UI rendering, values 2-100 are intermediate states, not final results. For performance reasons, Vue only renders the final value of 101.

Implemantation Strategy

Using schedulability combined with event loop concepts achieves this behavior:

  • Each counter change triggers the scheduler, adding the registered affect function to the pendingTasks queue. Due to Set's deduplication, only one function instance remains
  • Using Promise microtask characteristics, when the counter changes 100 times and all synchronous code completes, the then callback executes. At this point, the counter equals 101, and pendingTasks contains only one function, resulting in a single 101 output
const appState = reactive({
  counter: 1
})

const pendingTasks = new Set()
const promiseRef = Promise.resolve()
let isProcessing = false

const processPendingTasks = () => {
  if (isProcessing) {
    return
  }

  isProcessing = true
  // Microtask execution
  promiseRef.then(() => {
    pendingTasks.forEach((task) => task())
  }).finally(() => {
    // Reset flag after completion
    isProcessing = false
  })
}

effect(() => {
  console.log('counter:', appState.counter)
}, {
  scheduler (task) {
    // Add effect function to queue on each data change
    pendingTasks.add(task)
    // Attempt to flush tasks, but microtasks execute once per event loop
    // Even with 100 counter changes, only one effect executes
    processPendingTasks()
  }
})

let iterations = 100

while (iterations--) {
  appState.counter++
}
Tags: vue

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.