Skip to main content

Performance optimizations

Lazy evaluation​

Atoms are lazily evaluated. If an atom is created using a state initializer function, this function won't run until the .value getter is read, or the atom is subscribed for the first time.

const $atom = atom(() => {
console.log('I am lazily evaluated!')
return expensiveComputation(25)
})
// nothing's logged on the console yet

console.log($atom.value)
// Console: "I am lazily evaluated!"
// Console: 25

console.log($atom.value)
// Console: 25

You can make use of this feature to avoid expensive computations where possible.

Lazy evaluation in derived atoms​

A derived atom is not much different from a standard atom. Still, its state initializer function will wait for the atom's value to be requested in order to run.

const $alpha = atom(3)
const $beta = atom(5)

const $sum = atom((read) => {
console.log('Evaluation occurred')
return read($alpha) + read($beta)
})
// nothing's logged on the console yet

Later, when it's consumed for the first time:

console.log($sum.value)
// Console: "Evaluation occurred"
// Console: 8

console.log($sum.value)
// Console: 8

Dependency collection in derived atoms​

Dependency collection is another performance optimization that makes lazy evaluation much more advanced. When an atom is evaluated, it collects its latest dependencies. Since the $sum is evaluated at least once in our previous example, it's now "aware" that its dependencies are $alpha and $beta. Let's observe what will happen when those dependencies are updated:

$alpha.set(30)
$alpha.update((s) => s + 1)
$beta.set(1000)
// nothing's logged on the console yet

console.log($sum.value)
// Console: "Evaluation occurred"
// Console: 1031

console.log($sum.value)
// Console: 1031

Observe that $sum knew that it needs to rerun its state initializer when its .value is requested after the dependencies are changed. This can happen thanks to dependency collection. $sum knows that its internal state is invalid without causing evaluation. It can avoid evaluation until it's essential.

Lazy evaluation in atoms created with .map method​

Same kind of performance optimizations apply to the atoms that are created using the .map method.

const $count = atom(() => {
console.log('Ancestor atom evaluated')
return 100
})

const $doubleCount = $count.map((value) => {
console.log('Evaluation occurred')
return value * 2
})
// nothing's logged on the console yet

$count.update(s => s + 1)
// Console: "Ancestor atom evaluated"

console.log($doubleCount.value)
// Console: "Evaluation occurred"
// Console: 202

console.log($doubleCount.value)
// Console: 202

xoid supports a special kind of atoms called "stream"s. A stream is "an atom that may or may not have an immediate value". Lazy evaluation works slightly different in a "stream". See the next section for more.