Как правильно сочетать синхронный код и обещания? - PullRequest
0 голосов
/ 14 апреля 2020

Я пишу функцию, которая выполняет функцию посредника API между реальным вызовом API сервера и пользовательским пространством. Эта функция проверяет некоторые входные данные (syn c), преобразует их соответствующим образом (syn c), считывает некоторые другие данные (asyn c). Затем результаты объединяются и используются для окончательного фактического вызова конечной точки сервера (asyn c). Эта функция всегда должна возвращать обещание, и весь поток выполнения в высокоуровневых API, использующих его, должен обрабатываться через цепочку обещаний.

Однако у меня возникают проблемы с выяснением, правильно ли я это делаю. А также у меня есть несколько связанных вопросов, на которые я не могу найти ответ.

Это код, который я написал до сих пор:

import axios from "axios";
import * as MyAPI from "./MyAPI";
import querystring from "querystring";

function fetchFromServerEndpoint(route, routeParams, optQueryParams) {
    // Is it ok to use Promise.all() to combine sync and async code as below?
    return Promise.all([
        new Promise((resolve, reject) => {
            const routeReplacedParams = route.replace(/{([^{/]*?)}/g, (match, paramName) => {
                const paramValue = routeParams[paramName];

                if (paramValue === undefined) {
                    // Here I need to terminate execution of the replacer function
                    // and reject this promise
                    // Is it ok to `throw` here?
                    throw "UNDEFINED_ROUTE_PARAMETER";

                    // Can I somehow `return reject(...)` here instead of throw-ing?
                }

                return paramValue;
            });

            return resolve(routeReplacedParams);
        }),
        // Is it fine to use async to mock a promise with synchronous code?
        async function(){
            return querystring.stringify(optQueryParams);
        }),
        // This is part of a custom API, returns a legit promise
        MyAPI.getAuthorizationAsync(),
    ]).then(([finalRoute, queryParams, authToken]) => {
        // axios library, all APIs will return a promise
        return axios.get(`${finalRoute}?${queryParams}`, {
            headers: {
                Authorization: `Basic ${authToken}`
            }
        });
    });
}

Мои вопросы:

  1. Как правильно остановить выполнение блока кода, когда возникает ошибка из-за синхронной функции внутри обещания? Более того, как и в функции замены String.replace, можно ли «выбросить» туда? Можете ли вы предложить какой-либо лучший вариант, или, возможно, более благоприятный подход?
  2. Пометка функции с помощью async неявно создаст обещание, а также неявно преобразует (uncaught) throw с в Promise.reject() с и возвращает интро Promise.resolve() с. Преимущество состоит в том, что вы можете писать код идентично обычной синхронной функции, но при этом вести себя как обещание, так что вы можете связывать обещания с ним. Это мое понимание неверно? Есть ли недостатки (или преимущества, отличные от того, что я только что описал), о которых мне следует знать?
  3. Я использовал Promise.all (), чтобы объединить результаты нескольких независимых этапов выполнения функции. Я полагаю, это нормально, но есть ли другие способы справиться с этой ситуацией? Или, может быть, это плохая практика? Должен ли я вместо этого цеплять обещания и передавать результаты от обещания следующему до тех пор, пока я не достигну уровня, на котором мне нужно использовать их все?

Ответы [ 3 ]

1 голос
/ 14 апреля 2020

Как правильно остановить выполнение блока кода при возникновении ошибки из синхронной функции внутри обещания? Более того, как и в функции замены String.replace, можно ли «бросить» туда? Можете ли вы предложить какой-нибудь лучший вариант, или, может быть, подход, более обещающий подход? Функция executor Promise перехватывает синхронные исключения, возникающие в функции executor, и превращает их в отклоненное обещание.

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

Например, в этом случае при вызове randomSyncFunctionThatMightThrow() throws обещание вызывающего абонента будет отклонено автоматически, и обработчик .catch() будет hit.

function randomSyncFunctionThatMightThrow() {
    // other logic
    throw new Error("random error");
}

someFuncThatReturnsPromise().then(arg => {
    // bunch of logic here
    randomSyncFunctionThatMightThrow();
    // other logic here
    return someThing;
}).then(result => {
    console.log(finalResult);
}).catch(err => {
    console.log(err);
});

Пометка функции с помощью asyn c неявно создаст обещание, а также неявно преобразует (uncaught) броски в Promise.reject () и возвращает вводную Promise.resolve. () S. Преимущество состоит в том, что вы можете писать код идентично обычной синхронной функции, но при этом вести себя как обещание, так что вы можете связывать обещания с ним. Это мое понимание неверно?

Звучит правильно для меня. async функции имеют встроенную try/catch и автоматически перехватывают любые исключения и превращают их в отклоненное обещание.

Существуют ли недостатки (или преимущества, отличные от того, что я только что описал), которые я должен быть в курсе?

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

Я использовал Promise.all (), чтобы объединить результаты нескольких независимых этапов выполнения функции. Я полагаю, это нормально, но есть ли другие способы справиться с этой ситуацией? Или, может быть, это плохая практика? Должен ли я вместо этого цеплять обещания и передавать результаты от обещания до следующего, пока я не достигну уровня, на котором мне нужно использовать их все?

Promise.all() - для ситуаций, когда вы хотите иметь несколько независимых и параллельных асинхронных цепочек выполнения, все в полете одновременно, и вы просто хотите знать, когда все они будут выполнены и получить все результаты одновременно. Цепочка обещаний предназначена для тех случаев, когда вы хотите упорядочить вещи по порядку один за другим по любому числу причин, но часто потому, что шаг # 2 зависит от результата шага # 1. Вы должны выбрать Promise.all() вместо цепочки в зависимости от потребностей асинхронного кода (параллельное выполнение или последовательное выполнение).

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

Однако у меня возникают проблемы с выяснением, выполняю ли я это правильный путь.

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

Вот как я мог бы написать ваш код:

async function fetchFromServerEndpoint(route, routeParams, optQueryParams) {
    const finalRoute = route.replace(/{([^{/]*?)}/g, (match, paramName) => {
        const paramValue = routeParams[paramName];

        if (paramValue === undefined) {
            // Here I need to abort execution of the replacer function
            // parent promise will be rejected
            throw new Error("UNDEFINED_ROUTE_PARAMETER");
        }
        return paramValue;
    });
    const queryParms = querystring.stringify(optQueryParams);
    const authToken = await MyAPI.getAuthorizationAsync();
    return axios.get(`${finalRoute}?${queryParams}`, {
        headers: {
            Authorization: `Basic ${authToken}`
        }
    });
}

Если вам действительно не нравится await здесь вы можете избежать этого, изменив конец на:

    return MyAPI.getAuthorizationAsync().then(authToken => {
        return axios.get(`${finalRoute}?${queryParams}`, {
            headers: {
                Authorization: `Basic ${authToken}`
            }
        });
    });

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

0 голосов
/ 14 апреля 2020
  1. правильный способ завершить асинхронный блок - вернуть отклоненное обещание или выдать ошибку, что приведет к отклонению обещания с исключением «uncaught error», которое, как мы надеемся, содержит трассировку стека

  2. yes async синхронно возвращает обещание, аналогично тому, как get и set работают с синхронным доступом. есть схема использования thunks, где (в отличие от синхронного доступа) вы вызываете функцию «существительное» для фактического получения, например. foobar() вместо простого доступа к foobar. В любом случае, вы можете связывать обещания, но вводить вложенные блоки с вкладками, поэтому люди используют await, чтобы сохранить составную функцию плоской

  3. Promise.all - правильный способ остановить, пока не разрешатся все зависимые обещания и я видел документацию для Puppeteer, инициировавшего событие и одновременно добавившего слушателя, но я не уверен, что потоки имеют гарантированный порядок.

Существует два основных направления: асинхронные библиотеки JS (redux), называемые redux-thunk и sagas.

Redux thunk продвигает декларативные действия в виде цепочки обещаний

Sagas использует while-l oop опрос генераторов функций «возвращаемых итераторов» эффектов и позволяет меньше semanti c, но больше динамики c планирование. Мне не нравилось, что мы не могли начать саги напрямую; они работают только при прослушивании других действий, чтобы «пометить», например, dispatch('MY_EVENT_START'), который немедленно используется и слабо связан с действующей функциональностью (зарегистрированной как в диспетчере, так и в слушателе). Обычно, если блок Promise.all выходит из строя, остальные будут продолжать работать (и их зависимые цепочки), но sagas прекратит вызывать возвращенный итератор генератора, если он устарел или больше не нужен, et c. Одной из проблем, с которыми мы столкнулись при работе с sagas, было тестирование, поскольку для того, чтобы смоделировать сценарий, вам может потребоваться выполнить N шагов несколько раз, чтобы попасть туда.

0 голосов
/ 14 апреля 2020

Используйте await в блоке try catch. Это читабельно и не приводит к адскому вызову.

async function f() {

  try {
    let response = await fetch('http://no-such-url');
  } catch(err) {
    alert(err); // TypeError: failed to fetch
  }
}

f();

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await

...