是否可以将 axios.all 与 then() 一起用于每个承诺?

Is it possible to use axios.all with a then() for each promise?

我有一个 React 组件触发一个事件来获取数据。这导致动态数量的存储过程调用来获取数据,并且来自每个调用的数据存储在完全不同的位置。然后我需要在接收到所有数据并可用后重新渲染。我在 axios 中使用 promises。

由于 axios 调用的数量是动态的,我正在构建一个数组并将其插入到 axios.all 中,如下所示:

let promises = [];

for (let i = 0; i < requests.length; i++) {
    promises.push(axios.get(request[i].url, { params: {...} }));
}

axios.all(promises).then(/* use the data */);

问题是每个 axios 请求 returns 数据被添加到一个完全不同的地方的对象。由于我无法将它们全部放在一个 then 中的正确位置(我怎么知道哪个响应在哪个位置?),我尝试这样做:

let promises = [];

for (let i = 0; i < requests.length; i++) {
    promises.push(
        axios.get(request[i].url, { params: {...} })
            .then(response => {myObject[request[i].saveLocation] = response.data;})
    );
}

axios.all(promises).then(/* use the data */);

但是,这并不像我预期的那样有效。每个 get 之后的 then 被执行,但直到 then 附加到 axios.all 之后才执行。显然这是一个问题,因为我的代码试图在数据保存到对象之前使用它。

有没有办法为每个 axios.get 单独调用 axios.get,在其相应的 promise 被解析后执行,然后有一个最终的 then仅在 所有 的 promise 被解决后执行,现在对象已被填充后使用数据?

如果您第二次尝试的行为确实如此,则表明 axios 不符合 Promise/A+ 标准。 then 回调的 return 值必须是 then 所承诺 return 实现的值。由于这是您推入数组的承诺,因此 axios.all 将 return 用于该承诺的值只能通过首先执行 then 回调来获知。

即使您没有在 then 回调中明确 return 一个值,这也不会影响上述规则:在这种情况下 return 值是 undefined,一旦相应的承诺得到解决, 应该由 axios.all 提供的值。

具体参见规则 2.2.7、2.2.7.1、2.3.2.1、2.3.2.2 in the specs of Promise/A+:

2.2.7 then must return a promise.

promise2 = promise1.then(onFulfilled, onRejected);

2.2.7.1 If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).

[...]

To run [[Resolve]](promise, x), perform the following steps:

[...]

2.3.2 If x is a promise, adopt its state:

2.3.2.1 If x is pending, promise must remain pending until x is fulfilled or rejected.

2.3.2.2 If/when x is fulfilled, fulfill promise with the same value.

所以我建议改用 Promise/A+ 兼容的承诺实现。还有其他几个库,例如 request-promise.

或者,您可以使用本机 ES6 Promise 实现, 您自己。

ES6 提供 Promise.all 保证以与提供承诺相同的顺序提供已解析的值。

好的,所以我找到了一种方法来完成我需要的事情,而无需在每个 get 上使用 then。由于传递给 axios.get 的参数包含足够的信息来确定保存位置,并且由于我可以从响应中读回参数,所以我可以执行以下操作:

let promises = [];

for (let i = 0; i < requests.length; i++) {
    promises.push(axios.get(request[i].url, { params: {...} }));
}

axios.all(promises)
    .then(axios.spread((...args) => {
        for (let i = 0; i < args.length; i++) {
            myObject[args[i].config.params.saveLocation] = args[i].data;
        }
    }))
    .then(/* use the data */);

这可确保在使用对象之前接收到所有数据并将其保存到对象中。

如果您将 promise 传递给您的数组并附有各自的 then 函数

,您的初始代码可以正常工作
let promises = []; // array to hold all requests promises with their then
for (let i = 0; i < requests.length; i++) {
    // adding every request to the array
    promises.push(
        axios.get(request[i].url, { params: { ...} })
            .then(response => { myObject[request[i].saveLocation] = response.data; })
    );
}
// Resolving requests with their callbacks before procedding to the last then callback
axios.all(promises).then(/* use the data */);

看来在这 post 天,axios 建议使用 Promise.all 而不是 axios.all https://github.com/axios/axios 这对我有用,使用 Nuxtjs

async nuxtServerInit(vuexContext, context) {
    console.log(context);

    const primaryMenuData = {
      query: `query GET_MENU($id: ID!) {
        menu(id: $id, idType: NAME) {
          count
          id
          databaseId
          slug
          name
          menuItems {
            edges {
              node {
                url
                label
                target
              }
            }
          }
        }
      }`,
      variables: {
        "id": "Primary"
      }
    }

    const primaryMenuOptions = {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      data: primaryMenuData,
      url: 'http://localhost/graphql'
    };

    const postsData = {
      query: `query GET_POSTS($first: Int) {
        posts(first: $first) {
          edges {
            node {
              postId
              title
              date
              excerpt
              slug
              author {
                node {
                  name
                }
              }
              featuredImage {
                node {
                  altText
                  caption
                  sourceUrl(size: MEDIUM)
                }
              }
            }
          }
        }
      }`,
      variables: {
        "first": 15
      }
    }

    const postsOptions = {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      data: postsData,
      url: 'http://localhost/graphql'
    };
        
    try {
      const [primaryMenuResponse, postsResponse] = await Promise.all([
        await axios(primaryMenuOptions),
        await axios(postsOptions)
      ])
      
      vuexContext.commit('setPrimaryMenu', primaryMenuResponse.data.data.menu.menuItems.edges);
      vuexContext.commit('setPosts', postsResponse.data.data.posts.edges);

    } catch (error) {
      console.error(error);
    }      
  },