使用 axios 拦截器重试请求并将响应转发回前端

时间:2021-05-27 23:11:52

标签: node.js express error-handling axios reverse-proxy

关于我为一家公司完成的一项任务,我有一些技术问题,以便申请初级 Web 开发人员职位。截止日期已经在二月份,但我无法及时完成作业。我仍然想完成它,所以我在这里。

首先,我要描述应用程序的目的和功能。它用于为虚拟服装仓库的工人显示产品清单。目前,用户界面有一个下拉菜单,其中包含三个产品类别:手套、口罩和无檐小便帽。产品列表通过下拉菜单中的选择进行更新。

其次,数据来自两个独立的 API。这是 API 文档中的引述:

GET /v2/products/:category – Return a listing of products in a given category.
GET /v2/availability/:manufacturer – Return a list of availability info.
The APIs are running at https://bad-api-assignment.reaktor.com/.

第一个 API 返回产品数据,例如名称、颜色、价格和制造商。产品可用性数据(有货、缺货等)来自第二个 API。

最后,实际问题。我在处理来自提供可用性数据的 API 的错误响应时遇到问题。该 API 有一个内置的故意失败案例,其中一些响应以随机方式包含一个空数组(通常该数组包含大约 6000 个对象)。我正在考虑在 Node.js 后端处理这个错误情况。下面显示的后端代码仅适用于理想情况,不处理错误情况。

const express = require( 'express' )
const request = require( 'request' )
const app = express()
app.use( express.static( 'build' ))

/* Sets the needed CORS configuration to response header that is sent to the user's browser. */
app.use( ( req, res, next ) => {
    res.header( 'Access-Control-Allow-Origin', '*' )
    next()
} )

/* Forwards the frontend request to Reaktor Bad API server and the response back to frontend.
GET /v2/products/:category – Return a listing of products in a given category: gloves, facemasks or beanies.
GET /v2/availability/:manufacturer – Return a list of availability info.
The APIs are running at https://bad-api-assignment.reaktor.com/. */
app.get( '/api', ( req, res ) => {
    const category = req.query.category
    const manufacturer = req.query.manufacturer
    console.log( 'category query parameter: ', category )
    console.log( 'manufacturer query parameter: ', manufacturer )

    // Reaktor Bad API URL
    const baseUrl = 'https://bad-api-assignment.reaktor.com/v2'

    // craft full URL, make request to Bad API and forward response
    if ( category !== undefined ) {
        request( `${baseUrl}/products/${category}`, ( error, response ) => {
            console.error( 'category error:', error )
            console.log( 'category response & statusCode:', response && response.statusCode )
        } ).pipe( res )
    }
    else if ( manufacturer !== undefined ) {
        request( `${baseUrl}/availability/${manufacturer}`, ( error, response ) => {
            console.error( 'availability error:', error )
            console.log( 'availability response & statusCode:', response && response.statusCode )
        } ).pipe( res )
    }
    else {}
} )

const PORT = process.env.PORT || 3001
app.listen( PORT, () => {
    console.log( `Server running on port ${PORT}` )
} )

如您所见,我在指定路由时使用了 express 库。两个不同的前端请求可以以查询参数的形式到达 /api 路由:categorymanufacturer。根据查询参数,将适当的请求发送到 API。此外,路由中使用了现已弃用的请求库。我想用更新的替代品替换它。 request 首先从外部 API 请求数据,然后将其传递给前端。据我了解,这称为反向代理。在前端,我正在等待所有可用性数据到达(使用 axios),然后使用产品数据和可用性数据构建一个数组。然后将该数组呈现为应用用户界面中显示的产品列表。

我尝试了几种方法来解决这个问题,最近我了解了 axios 拦截器。它们是否可用于重试导致错误响应(空数组)的请求以及如何使用?反向代理怎么样?可以用 axios 来完成吗?我已经阅读了 axios 文档,但不明白。使用 express 和 axios 的动机是因为我已经对它们有些熟悉并且不推荐使用请求库。任何帮助将不胜感激。由于我仍在学习这些东西,因此不仅有解决方案代码而且还说明其功能的响应对我来说是最有益的。提前致谢!

1 个答案:

答案 0 :(得分:0)

我做了一个更简单的,但没有使用 Axios 拦截器,我只是重试失败的承诺,如果你有更复杂的条件,它可以扩展,但基本实现是这样的。

/**
 * Sleeps for a given number of time
 * @param milliseconds milliseconds to sleep
 * @returns void promise
 */
export function sleep(milliseconds: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

/**
 * Wraps a promise like to add retry capability.  This is resursive
 * @param fn promise to wrap
 * @param retries retries remaining
 * @param interval interval between retries
 * @returns promise
 */
export async function retryable<T>(
  fn: PromiseLike<T>,
  retries: number = 3,
  interval: number = 200
): Promise<T> {
  try {
    return await fn;
  } catch (error) {
    if (retries === 0) {
      throw error;
    }
    await sleep(interval);
    return await retryable(fn, retries - 1, interval);
  }
}

我在 Axios 中使用的是一个会导致错误的拦截器。这样我就可以在客户端模拟故障,而不是在服务器端创建场景。

    if (failureRate > 0) {
      authenticatedClientRef.current.interceptors.request.use((req) => {
        if (Math.random() < failureRate) {
          throw Error('Intentional failure');
        } else {
          return req;
        }
      });
    }