#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. Ах, они сделали, спасибо. Особенно последний. Просто возвращает новый объект, ничего не копируя.