@AstraVlad
Финансист, консультант, программист-любитель

Асинхронность и чистые функции несовместимы?

Правильно ли я понимаю, что начав работать с асинхронными запросами нужно забыть про чистые функции? Потому что "нельзя просто взять и вернуть значение из промиса" и нужно передавать его в коллбэк чтобы уже там записать во внешнюю (по отношению к коллбэку) переменную? И именно поэтому во всех примерах ставят бестолковый console.log(data), потому что что-то реально полезное требует изрядного нагромождения кода?
  • Вопрос задан
  • 248 просмотров
Пригласить эксперта
Ответы на вопрос 4
Rsa97
@Rsa97
Для правильного вопроса надо знать половину ответа
async/await
Но чистые функции всё равно не всегда получатся, поскольку асинхронность используется в основном для запроса к внешним ресурсам и, соответственно, функция будет недетерминированой.
Ответ написан
Kozack
@Kozack Куратор тега JavaScript
Thinking about a11y
Функция возвращает промис. И что в этом такого?
function getData() {
    return fetch( ... )
}

function filterData(data) {
  return data.filter( ... )
}

function renderFilteredData(filteredData) {
  // ...
}

function main() {
    const dataPromise = getData()

    const filteredDataPromise = dataPromise.then(filterData)

    filteredDataPromise.then(renderFilteredData)

    // Или попроще
    getData()
      .then(filterData)
      .then(renderFilteredData)
}

Работая с асинхронностью вы не можете в привычном смысле работать с данными, но вы можете "Планировать" выполнение каких-то функций
Ответ написан
bingo347
@bingo347 Куратор тега JavaScript
Ткнуть в доку лучше готового к копипасте ответа
Чистота функции определяется двумя условиями:
1. Функция детерминирована
2. Функция не имеет побочных эффектов
Если оба этих условия истинны, то тогда функцию можно считать чистой.

Детерминированность функции означает, что любому набору входных данных можно однозначно сопоставить результат и этот результат будет всегда таким для данного набора аргументов, независимо ни от чего. То есть умножение, сложение, нахождение максимума - это все детерминированные функции, а вот функции вычисляющие псевдослучайное число, текущую дату или температуру с датчика - нет, так как зависят от внешних изменяющихся условий.

Побочные эффекты - это нечто, что меняет окружающую среду, например запись на диск или в чужую (не аллоцированную самой функцией) память, сетевой запрос, запуск процесса или потока, отправка сообщения другому потоку, синхронизация потоков и т.д.

Из этого становится верно утверждение, что если вызов чистой функции заменить на ее результат, то поведение программы от этого не изменится. А от сюда напрашивается еще один вывод, что программа состоящая только из чистых функций бесполезна. Но можно вынести все грязные операции на край приложения, а бизнес логику описать в виде чистых функций, что даст ей все преимущества таких функций, вроде предсказуемости и простой тестируемости.

Что же качается асинхронности, то большинство асинхронных операций (если не все) порождают побочные эффекты. Но можно выносить их на край приложения, а основную логику строить в виде чистых функций. Покажу на примере такого сценария:
1. Пользователь кликает на кнопку
2. Данные из стейта подготавливаются и отправляется запрос на сервер с ними
3. Когда пришел ответ данные снова преобразуются и обновляется стейт
let state = {
    // для простоты будем считать что эти данные уже есть
    formData: {
        text1: 'Hello',
        text2: 'world',
        checkbox1: true,
        checkbox2: false
    },
    // а сюда мы должны положить результат запроса
    requestResult: null
};
// querySelector не детерминирован, а подписка на событие - побочный эффект
document.querySelector('.button').addEventListener('click', clickHandler);

// prepareFormData - чистая функция
function prepareFormData(formData) {
    return {
        text: `${formData.text1} ${formData.text2}`,
        checkboxes: [
            ['checkbox1', formData.checkbox1],
            ['checkbox2', formData.checkbox2]
        ].filter(([, v]) => v).map(([v]) => v)
    };
}

const API_URL = '/api/data';

// createRequestSender - чистая функция
// API_URL - константа, она не меняет результат
// сама createRequestSender не делает запроса к api
// она просто "вычисляет" из preparedFormData функцию, притом детерминировано
function createRequestSender(preparedFormData) {
    const body = JSON.stringify(preparedFormData);
    // а вот возвращаемая функция уже грязная:
    // хотя она и вполне детерминирована,
    // но она вызывает fetch и response.json имеющие побочные эффекты
    // но это никак не влияет на чистоту внешней функции createRequestSender, так как та ее не вызывает
    return async () => {
        const response = await fetch(API_URL, {
            method: 'POST',
            body
        });
        const result = await response.json();
        return result;
    };
}

// mapResponseToState - чистая функция,
// а вот если бы мы напрямую изменили state вместо копирования его в результат
// это бы был побочный эффект, так как state не принадлежит mapResponseToState
function mapResponseToState(state, responseResult) {
    return {
        ...state,
        requestResult: (responseResult.ok
            ? {
                showSuccess: true,
                showError: false,
                text: responseResult.result
            }
            : {
                showSuccess: false,
                showError: true,
                text: responseResult.error
            }
        )
    };
}

// обработчик клика clickHandler будет тем самым грязным краем
async function clickHandler() {
    // чистая операция
    const requestSender = createRequestSender(prepareFormData(state.formData));
    // побочный эффект
    const result = await requestSender();
    // еще чистая операция
    const newState = mapResponseToState(state, result);
    // и снова побочный эффект
    state = newState;
}
Ответ написан
yurakostin
@yurakostin
Front-end developer
Здравствуйте.

Курите функциональное программирование. Дмитрий Беляев вам в целом всё правильно сказал: запрос, вывод в консоль, чтение из глобальной области видимости, запись в файл и всё прочее - сайд эффекты, которые в идеале нужно выполнять на границе "чистого" и "грязного" миров. Во фронтенд разработке, к сожалению, эта граница практически отсутствует, так как всё время есть какие-то события, какие-то запросы туда-сюда, и вот это вот всё, но тем не менее, вы можете писать код так, чтобы он был лучше, чище, понятнее; был прост в поддержке, и т д.

По вашему вопросу. Логика асинхронных операций в библиотеке типа fp-ts вынесена в Task, и все преобразования над данными можно реализовывать чисто.

Но начать лучше, пожалуй, с наиболее адекватного введения в ФП. Будьте готовы к тому, что мозг будет отказываться воспринимать информацию, и если вам не зайдёт - возвращайтесь в ФП позже. Это немного другой мир, но он по-своему прекрасен.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы
Artezio Нижний Новгород
от 130 000 до 180 000 ₽
Artezio Москва
от 160 000 до 220 000 ₽
Artezio Санкт-Петербург
от 160 000 до 220 000 ₽