结合同步代码和Promise的正确方法是什么?

时间:2020-04-13 22:08:26

标签: javascript error-handling async-await es6-promise

我正在编写一个函数,该函数充当实际服务器API调用和用户态之间的API中介函数。 此函数验证一些输入数据(同步),将其适当转换(同步),读取其他一些数据(异步)。然后将结果合并并用于最终实际调用服务器端点(异步)。 此函数必须始终返回诺言,使用该函数的更高级别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. 当在promise中由于同步函数出现错误情况时,停止执行代码块的正确方法是什么?而且,就像在String.replace的替换函数中一样,可以“扔”在那里吗?您能否提出任何更好的选择,或者是一种更可行的承诺方法?
  2. async标记一个函数将隐式地创建一个诺言,还将隐式地将throw s转换(捕获)为Promise.reject() s并返回前导Promise.resolve() s。好处是您可以与普通的同步函数相同地编写代码,但是仍然可以像一个Promise一样操作,因此可以将Promise链接到它。对我的这种理解不正确吗?我应该注意哪些缺点(或好处,除了我刚才描述的以外)?
  3. 我使用Promise.all()来组合函数执行的多个独立阶段的结果。我认为这很好,但是还有其他方法可以解决这种情况吗?也许这是一个不好的做法?相反,我是否应该将链式诺言和从诺言的传递结果传递到下一个诺言,直到达到需要使用所有这些诺言的水平?

3 个答案:

答案 0 :(得分:1)

当在promise中由于同步函数出现错误情况时,停止执行代码块的正确方法是什么?此外,就像在String.replace的替换函数中一样,可以“扔”在那里吗?您能否提出任何更好的选择,或者是一种更可行的承诺方式?

可以扔在那里。 Promise执行程序功能的确捕获执行程序功能中发生的同步异常,并将它们转变为被拒绝的承诺。

这是否是一件好事,实际上取决于您想发生的事情。如果您希望调用函数中的promise拒绝,那么您可以从同步函数中抛出,这将在异步调用者中冒泡到promise并导致拒绝。否则,您还可以自由使用常规的同步技术,例如从同步函数返回错误条件,在调用方中检查该错误,然后采取相应措施。因此,与任何其他设计一样,它完全取决于您希望如何调用同步函数以及调用者应如何检查错误或处理错误。有时返回的错误代码是正确的,有时抛出异常是正确的。

例如,在这种情况下,如果调用randomSyncFunctionThatMightThrow()引发,则调用方的promise将自动被拒绝,并且.catch()处理程序将被击中。

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);
});

将函数标记为异步将隐式创建一个Promise,还将隐式转换(未捕获)抛出为Promise.reject()s并返回Intro Promise.resolve()s。好处是您可以与普通的同步函数相同地编写代码,但是仍然可以像一个Promise一样操作,因此可以将Promise链接到它。对我的这种理解不正确吗?

对我来说听起来不错。 async函数具有try/catch内置功能,可以自动捕获任何异常并将其转变为被拒绝的承诺。

我应该意识到还有哪些弊端(或好处,除了我刚刚描述的那样)?

强制所有同步代码与promises异步处理正常的普通返回结果有其局限性。由于明显的编码复杂性原因,您不会在整个程序中将其用于每个同步功能。异步代码花费更多的时间来编写和测试,因此我不去寻找使普通的同步事物开始返回promise并使所有调用者异步处理的方法。如果某些同步代码也处理承诺,那么有时可能会更容易将它们与异步代码混合。但是,这不是同步代码的常规用法。

我使用Promise.all()来组合函数执行的多个独立阶段的结果。我认为这很好,但是还有其他方法可以解决这种情况吗?也许这是一个不好的做法?相反,我是否应该将链式诺言和从诺言的传递结果传递到下一个诺言,直到达到需要使用所有这些诺言的水平?

Promise.all()适用于以下情况:您希望同时进行多个独立且并行的异步执行链,并且只想知道何时完成并立即获得所有结果。承诺链接适用于出于多种原因(但通常是因为步骤2取决于步骤1的结果)而要一个接一个地进行排序的情况。您应该根据异步代码的需要(并行执行与顺序执行)来选择Promise.all()与链接。

将同步代码放入Promise.all()可以很好地工作(您甚至不必将其包装在promise中,因为Promise.all()也会接受运行函数的返回值),但是通常, 毫无意义。同步代码是阻塞和同步的,因此通常通过将同步代码放在Promise.all()中不会获得任何并行性。我通常只在Promise.all()之前或之后运行同步代码,而不将其与实际的异步承诺混在一起。

但是,我无法确定自己是否以正确的方式进行操作。

我在下面提供了一个替代实现。通常,我不喜欢将同步代码包装在Promise中,所以这是我的选择。我已经使用了async函数,所以我可以使用await,以便同步异常自动转换为拒绝的Promise,并且您函数的合同已经返回了Promise,因此这只是一种更简单的实现方式同样的结果(在我看来)。当然,这只是编码风格,很大程度上受制于意见。

这就是我可能会编写您的代码的方式:

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是一个有用的工具(如果使用得当,可以使您编写更简单的代码)。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

  1. 终止异步块的正确方法是返回被拒绝的promise或引发错误,这将拒绝带有“未捕获错误”异常的promise,该异常有望包含堆栈跟踪

  2. async同步返回一个承诺,类似于getset处理同步访问的方式。有一种使用thunk的模式,其中(相对于同步访问)您调用“名词”函数来实际派生例如。 foobar(),而不是仅访问foobar。无论如何,您都可以链接Promise,但要输入嵌套的选项卡式块,因此人们使用await来使撰写函数保持平坦

  3. Promise.all是停止所有依赖的诺言之前的正确方法,而且我已经看到Puppeteer的文档初始化了一个事件并同时添加了一个监听器,但是我不确定是否线程有保证的顺序。

有两个主流的异步JS(redux)库,分别称为redux-thunksagas

Redux thunk促进了声明式承诺链式行动

Sagas使用对函数生成器返回的迭代器产生的“效果”进行while循环轮询,并允许较少的语义,但允许更多的动态调度。我不喜欢我们不能直接踢萨加斯。它们只能通过侦听其他动作来与“ dispatch('MY_EVENT_START')”一起“标记”,而Promise.all被立即消耗并与运行的实际功能(在调度程序/侦听器中都注册)松散耦合。通常,如果SELECT th.* FROM (SELECT date_part('day', tpep_pickup_datetime) AS trip_day, date_part('hour', tpep_pickup_datetime) AS trip_hour, count(*) AS numbers, row_number() over (partition by date_part('day', tpep_pickup_datetime) order by count(*) desc) as seqnum FROM nyc_yellow_2019_01 GROUP BY trip_day, trip_hour ) th WHERE seqnum = 1; 块发生故障,其余的将继续运行(及其相关链),但如果过时或不再需要sagas,则它们将停止调用生成器的返回迭代器,等等。正在测试,因为要模拟场景,您可能需要走N次才能到达那里。