Почему я не могу использовать let! (пусть удар) внутри выражения try/with?

Я вызываю асинхронную функцию из вычислительного выражения task {...}. Обычно я могу использовать let! для вызова асинхронных функций и ожидания их результатов. Однако, если я перенесу вызов функции в выражение try/with, я получу следующую ошибку компилятора.

This construct may only be used within computation expressions

Поскольку этот код все еще находится в выражении task{...}, я не уверен, что именно имеет в виду компилятор.

В следующем надуманном примере вызов getDataMayThrowAsync из controller1 компилируется, но в controller2 я получаю указанную выше ошибку компиляции.

module LetBangIssue =

    open System
    open System.Threading.Tasks

    let getDataMayThrowAsync (id:int) = task {
        do! Task.Delay(10)
        let random = Random()
        if random.Next(1, 1000) < 500 then
            raise <| Exception "Something went wrong"
            
        return "the data"
    }

    // Compiles, but doesn't handle error case...
    let controller1() = task {
        let! tryGetTheData = getDataMayThrowAsync 10
        return tryGetTheData
    }

    // Attempt to handle error case, but can't compile...
    let controller2 () = task {
        let theData =
            try
                let! tryGetTheData = getDataMayThrowAsync 10
                tryGetTheData
            with
            | ex ->
                printfn $"Error: {ex.Message}"
                "couldn't get the data"
                
        return theData
    }

Как я могу исправить controller2, чтобы решить ошибку компиляции?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема не в try/with. Это дополнительный let. Попробуй это:

let controller2 () = task {
    let theData =
            let! tryGetTheData = getDataMayThrowAsync 10
            tryGetTheData
            
    return theData
}

Это не работает с той же ошибкой, потому что в теле theData вы используете let! без окружения task { }.

Как написано, дополнительный let вообще не нужен, так как вы просто возвращаете его в конце. Так что это будет работать нормально:

let controller2 () = task {
    try
        let! tryGetTheData = getDataMayThrowAsync 10
        return tryGetTheData
    with
    | ex ->
        printfn $"Error: {ex.Message}"
        return "couldn't get the data"
}

Но если вам действительно нужен дополнительный let, вы должны сделать его let! (потому что он привязан к асинхронному результату), вы должны обернуть его тело в task (иначе вы не сможете использовать let! внутри него), и вы должны используйте return, чтобы вернуть значения из него:

let controller2 () = task {
    let! theData = task {
        try
            let! tryGetTheData = getDataMayThrowAsync 10
            return tryGetTheData
        with
        | ex ->
            printfn $"Error: {ex.Message}"
            return "couldn't get the data"
    }
            
    return theData
}

Отлично! Можно вопрос вдогонку? Одной из моих попыток исправить ошибку компиляции была let tryGetTheData = getDataMayThrowAsync 10 |> Async.AwaitTask |> Async.RunSynchronously. Казалось, что это сработало, но не похоже на правильный подход. Я не уверен, почему. Являются ли асинхронные функции неуместными в таком случае?

Matthew MacFarland 23.11.2022 15:25

Ваша попытка сделала все это синхронным. Вместо того, чтобы перехватывать результаты вычислений через продолжения (также известные как «обратные вызовы»), ваше решение остановит поток и дождется завершения асинхронной задачи, а затем продолжит.

Fyodor Soikin 23.11.2022 15:35

О да. Я вижу это сейчас. Похоже, что Async.AwaitTask |> Async.RunSynchronously похож на GetAwaiter().GetResult() в C#.

Matthew MacFarland 23.11.2022 15:55

Да, точно. За исключением того, что я думаю, что это немного более явно о том, что происходит из-за того, что в названии есть «Синхронно».

Fyodor Soikin 23.11.2022 15:59

Другие вопросы по теме