首页 > 解决方案 > async/await 循环在 axios.get 中按预期工作,但不适用于所需的本地 .json 文件

问题描述

我真的很想知道我在这里缺少什么 javascript 概念(关于 async/await)。我很确定这不是一个重复的问题。

我的代码涉及太多,无法作为示例显示,因此我将尝试尽我所能描述它并以最简单的形式显示问题。这段代码的主要目的是并行发出一组网络请求,并在它们都完成后执行一些操作。我让它正常工作,循环执行“似乎”暂停,直到返回等待的值,这是需要的。

但是,当我使用本地 .json 文件(通过 加载require)而不是 usingaxios.get时,循环会在返回等待的值之前一直运行。这是有问题的,因为我在循环暂停的前提下改变了等待的值。

/* 
  Simplified as much as possible.
  Note: The code works as desired when globalOptions.useNetworkStub = false
*/
const axios = require('axios').default;

const globalOptions = {
  useNetworkStub: true
}

const getSearchByTerm = async(term) => {
  if (globalOptions.useNetworkStub) {
    const networkStub = require('./fake-response.json')
    return Promise.resolve(networkStub)
  }

  return axios.get('https://jsonplaceholder.typicode.com/todos/1')
}

const getSearchesByTerms = async(terms = ['cats','dogs']) => {
  const results = []
  let result
  try {
    for (let i = 0; i < terms.length; i++) {
      const term = terms[i]
      result = await getSearchByTerm(term)
      result.data && (result.data.searchTerm = term) // The issue is here!
      results.push(result)
    }
  } catch (err) {
    return Promise.reject(`getSearchesByTerms() Failed: ${err}`)
  }

  // ... code truncated here to keep things simple ...

  return Promise.resolve(results)
}

getSearchesByTerms()
  .then((responses) => {
    let merged = {responses: []}
    for (const response of responses) {
      merged.responses.push(response.data)
    }
    console.log(`SUCCESS, data: ${JSON.stringify(merged, null, 2)}`)
  })
  .catch(e=>console.log(e))

假响应.json

{
  "data": {
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
 }
}

正如我之前提到的,当使用 axios 时,最终结果是正确的。第一个响应有一个键值对,searchTerm: 'cats'第二个响应有一个键值对searchTerm: 'dogs'

当使用本地 .json 文件时,第一个和第二个响应都具有相同的键值对searchTerm: 'dogs'. 这就是问题。

编辑:更改const term = terms[i].termconst term = terms[i]

另一个编辑:修复代码中的拼写错误,添加数据并在 repl.it 上发布fake-response.json问题的工作示例

标签: javascriptnode.jsasync-awaitaxios

解决方案


减去一些拼写错误,您的代码可以运行——但存在一些问题。

// example.js
const axios = require('axios').default;

const globalOptions = {
  useNetworkStub: true
}

const getSearchByTerm = async function(term) {
  if (globalOptions.useNetworkStub) {
    const networkStub = require('./fake-response.json')
    return Promise.resolve(networkStub)
  }

  return axios.get('https://some-live-endpoint.com/', {params:{q: term}})
}

const getSearchesByTerms = async function(terms = ['cats','dogs']) {
  const results = []
  let result
  try {
    for (let i = 0; i < terms.length; i++) {
      const term = terms[i]
      result = await getSearchByTerm(term)
      console.log("Term: ", term, "Result: ", result); // added this log to clarify your issue
      result.data && (result.data.searchTerm = term) // The issue is here!
      results.push(result)
    }
  } catch (err) {
    return Promise.reject(`getSearchesByTerms() Failed: ${err}`)
  }

  // ... code truncated here to keep things simple ...

  return Promise.resolve(results)
}

getSearchesByTerms()
  .then((responses) => {
    let merged = {responses: []}
    for (const response of responses) {
      merged.responses.push(response.data)
    }
    console.log(`SUCCESS, data: ${JSON.stringify(merged, null, 2)}`)
  })
  .catch(e=>console.log(e))

这是和示例 json 文件

{
    "data": {
        "payload": "Success"
    }
}

这是您将获得的输出:

Term:  cats Result:  { data: { payload: 'Success' } }
Term:  dogs Result:  { data: { payload: 'Success', searchTerm: 'cats' } }
SUCCESS, data: {
  "responses": [
    {
      "payload": "Success",
      "searchTerm": "dogs"
    },
    {
      "payload": "Success",
      "searchTerm": "dogs"
    }
  ]
}

需要注意的是,您的问题不在于async两个结果都使用对同一对象的引用。这是一堂课,可以让您在 Javascript 和许多其他语言中以许多微妙但重要的方式烧伤您,这些语言对程序员隐藏了指针的复杂性。您通常应该避免改变对象。

这是一个使用 JS 传播语法来复制对象而不是变异的版本。

const axios = require('axios').default;

const globalOptions = {
  useNetworkStub: true
}

const getSearchByTerm = async function(term) {
  if (globalOptions.useNetworkStub) {
    const networkStub = require('./fake-response.json')
    return Promise.resolve(networkStub)
  }

  return axios.get('https://some-live-endpoint.com/', {params:{q: term}})
}

const getSearchesByTerms = async function(terms = ['cats','dogs']) {
  const results = []
  let result
  try {
    for (let i = 0; i < terms.length; i++) {
      const term = terms[i]
      result = await getSearchByTerm(term)
      console.log("Term: ", term, "Result: ", result); // added this log to clarify your issue
      if (result && "data" in result) {
        results.push({ data: { ...result.data, term  }}) // copies instead of mutating original object
      }
    }
  } catch (err) {
    return Promise.reject(`getSearchesByTerms() Failed: ${err}`)
  }

  // ... code truncated here to keep things simple ...

  return Promise.resolve(results)
}

getSearchesByTerms()
  .then((responses) => {
    let merged = {responses: []}
    for (const response of responses) {
      merged.responses.push(response.data)
    }
    console.log(`SUCCESS, data: ${JSON.stringify(merged, null, 2)}`)
  })
  .catch(e=>console.log(e))

这是一个以您希望它工作的方式发生变异的版本。重要的变化是存根有多个可以查询的对象:

// newExample.js
// gets a new object each time, so mutation doesn't break
const axios = require('axios').default;

const globalOptions = {
  useNetworkStub: true
}

const getSearchByTerm = async function(term) {
  if (globalOptions.useNetworkStub) {
    const networkStub = require('./fake-response.json')[term]
    return Promise.resolve(networkStub)
  }

  return axios.get('https://some-live-endpoint.com/', {params:{q: term}})
}

const getSearchesByTerms = async function(terms = ['cats','dogs']) {
  const results = []
  let result
  try {
    for (let i = 0; i < terms.length; i++) {
      const term = terms[i]
      result = await getSearchByTerm(term)
      console.log("Term: ", term, "Result: ", result); // added this log to clarify your issue
      result.data && (result.data.searchTerm = term) // no longer mutating same object
      results.push(result)
    }
  } catch (err) {
    return Promise.reject(`getSearchesByTerms() Failed: ${err}`)
  }

  // ... code truncated here to keep things simple ...

  return Promise.resolve(results)
}

getSearchesByTerms()
  .then((responses) => {
    let merged = {responses: []}
    for (const response of responses) {
      merged.responses.push(response.data)
    }
    console.log(`SUCCESS, data: ${JSON.stringify(merged, null, 2)}`)
  })
  .catch(e=>console.log(e))

// fake-response.json
{
    "cats": {
        "data": {
            "payload": "Success for cats!"
        }
    },
    "dogs": {
        "data": {
            "payload": "Success for dogs!"
        }
    }
}

仍然。如果你担心深度克隆——我建议你以一种不需要改变或克隆值的方式来规划你的输出:

// better.js
// plans output to not mutate or copy
const axios = require('axios').default;

const globalOptions = {
  useNetworkStub: true
}

const getSearchByTerm = async function(term) {
  if (globalOptions.useNetworkStub) {
    const networkStub = require('./fake-response.json')[term]
    return Promise.resolve(networkStub)
  }

  return axios.get('https://some-live-endpoint.com/', {params:{q: term}})
}

const getSearchesByTerms = async function(terms = ['cats','dogs']) {
  const results = []
  let result
  try {
    for (let i = 0; i < terms.length; i++) {
      const term = terms[i]
      result = await getSearchByTerm(term)
      console.log("Term: ", term, "Result: ", result); // added this log to clarify your issue
      if (result && "data" in result) {
        results.push({ term, data: result.data }) // doesn't copy or mutate result
      }
    }
  } catch (err) {
    return Promise.reject(`getSearchesByTerms() Failed: ${err}`)
  }

  // ... code truncated here to keep things simple ...

  return Promise.resolve(results)
}

getSearchesByTerms()
  .then((responses) => {
    let merged = {responses: []}
    for (const response of responses) {
      merged.responses.push(response) // grabbing while response
    }
    console.log(`SUCCESS, data: ${JSON.stringify(merged, null, 2)}`)
  })
  .catch(e=>console.log(e))

推荐阅读