цикл async / await работает, как ожидалось, с axios.get, но не с требуемым локальным файлом.файл json

#javascript #node.js #async-await #axios

#javascript #node.js #async-await #axios

Вопрос:

Я действительно хочу знать, какие концепции javascript (в отношении async / await) Я здесь отсутствует. Я уверен, что это не дублирующий вопрос.

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

Однако, когда я использую локальный .файл json (загружается через require ) вместо того, чтобы использовать axios.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 amp;amp; (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))
 

fake-response.json

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

Как я отмечал ранее, при использовании axios конечный результат является правильным. Первый ответ имеет пару ключ-значение searchTerm: 'cats' , а второй ответ имеет пару ключ-значение searchTerm: 'dogs'

Когда локальный .файл json используется, и первый, и второй ответы имеют одну и ту же пару ключ-значение searchTerm: 'dogs' . В этом проблема.

РЕДАКТИРОВАТЬ: изменено const term = terms[i].term на const term = terms[i]

ДРУГОЕ РЕДАКТИРОВАНИЕ: исправлены опечатки в коде, добавлены данные для fake-response.json и опубликован рабочий пример этой проблемы здесь на repl.it

Комментарии:

1. const term = terms[i].term Правильно? Похоже, это должно привести к ошибке. Должно быть const term = terms[i] или я что-то упускаю?

2. Проблема, вероятно, в том, что require('./fake-response.json') каждый раз возвращается один и тот же объект, в то время как axios будет создавать новый объект для каждого ответа. Ничего общего с async / await .

3. Я не вижу, были ли fake-response.json изменения где-либо в коде. Также было бы лучше показать содержимое fake-response.json

4. @patriktor kildsen Спасибо. Я отредактировал код. Просто побочный продукт написания упрощенного кода с нуля без его тестирования.

5. @Anatoly Ответ json изменяется в строке result.data amp;amp; (result.data.searchTerm = term) . fake-response.json слишком велико для отображения, но значения в нем являются случайными, поэтому это не имеет значения.

Ответ №1:

За вычетом нескольких опечаток, ваш код выполняется — с некоторыми проблемами.

 // 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 amp;amp; (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 amp;amp; "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 amp;amp; (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 amp;amp; "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))
 

Комментарии:

1. У меня такое чувство, что это была проблема. Я решил изменить данные, потому что объект глубокий и большой, и, насколько я понимаю, распространение не будет выполнять глубокую копию. Я также подумал, что любая утилита, которую я мог бы добавить для глубокой копии, добавила бы накладные расходы, которых я не хотел. Есть предложения?

2. Я попробовал ваше исправление, и оно, конечно, работает, но я озадачен тем, как оператор распространения копирует объект, вложенный более чем на один уровень. Пример здесь на repl.it

3. В многомерных массивах есть ограничения, о которых вы можете прочитать в документации

4. Я добавил несколько примеров, которые, надеюсь, прояснят ситуацию.

5. Ах, они сделали, спасибо. Особенно последний. Просто возвращает новый объект, ничего не копируя.