Skip to content

RAN012 Reactive read after the first await of an async memo

warning RAN012: userId.Value is read after the first let!/do! of this async memo; a read after an await that suspends is untracked, and a change to userId starts no flight.

Cause. An async memo tracks the reads its body makes while it runs synchronously, up to the first await that suspends. A read in a continuation is untracked, so a change to that source starts no flight. A let! on a task that has already completed does not suspend; the rule reports it too. The rule follows an await in a bind's continuation and after an if, loop or try whose body awaits, and a read anywhere in a loop whose body awaits runs after the previous iteration's await. An await counts when it belongs to the memo's own computation. A read in the body of a memo, effect, boundary or async value created after the await, or under untrack, is tracked by that body and is not reported; a read in createRoot's body, createEffectOn's act or an onCleanup callback is.

Fix. Read the source before the first await and bind it to a local.

Triggers

module Example

open System.Threading.Tasks
open Ranvier

let profile (userId: Signal<int>) =
    createAsync (fun _ _ ->
        task {
            do! Task.Yield ()
            return userId.Value
        })

Fixed

module Example

open System.Threading.Tasks
open Ranvier

let profile (userId: Signal<int>) =
    createAsync (fun _ _ ->
        task {
            let id = userId.Value
            do! Task.Yield ()
            return id
        })

Suppression. // fsharpanalyzer: ignore-line RAN012, with the reason on the line before.

Edit this page