Naprawdę chcę wiedzieć, jakie koncepcje JavaScript (w sprawie asynchronizacji / oczekiwania) tutaj brakuje. Jestem pewien, że nie jest to duplikate pytanie.

Mój kod jest zbyt zaangażowany, aby pokazać jako przykład, więc spróbuję opisać go najlepiej, jak mogę i pokazać problem w swojej najprostszej formie. Głównym celem niniejszego Kodeksu jest wykonanie zestawu żądań sieciowych równolegle i wykonywać niektóre działania po ich zakończeniu. Mam właściwe działanie, wykonanie pętli "pojawia się", aby wstrzymać, aż oczekiwana wartość zostanie zwrócona, a to jest pożądane.

Jednak kiedy używam lokalnego pliku Jest to problematyczne, ponieważ zmutowuję oczekiwaną wartość opartą na założeniu pętli zatrzymującą się.

/* 
  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))

Fake-response.json.

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

Jak wspomniałem wcześniej, gdy AXIOS jest używany, wynik końcowy jest poprawny. Pierwszą odpowiedzią ma parę wartości kluczowej searchTerm: 'cats', a druga odpowiedź ma parę wartości kluczowej searchTerm: 'dogs'

Gdy lokalny plik .json jest używany zarówno pierwsze, jak i drugie repony mają tę samą parę wartości klucza searchTerm: 'dogs'. To jest problem.

Edytuj: Zmieniono const term = terms[i].term do const term = terms[i]

Kolejna edycja: Naprawiono literówki w kodzie, dodano dane dla fake-response.json i opublikował przykład pracy tego problemu Oto na REP.IT

1
apena 23 listopad 2020, 00:06

1 odpowiedź

Najlepsza odpowiedź

Minus kilka literówek, biegnie kod - z pewnymi problemami.

// 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))

I tutaj i przykład plik JSON

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

Oto wyjście, które otrzymasz:

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"
    }
  ]
}

To, co do zauważenia jest to, że twój problem nie jest async Używa odniesienia do tego samego obiektu dla obu wyników. Jest to lekcja, która może spalić Cię w wielu subtelnych, ale ważnych sposobach w JavaScript - i wiele innych języków, które ukrywają złożoność wskaźników z programatora. Należy ogólnie unikać przycisku obiektów.

Oto wersja, która wykorzystuje składnię JS Spread, aby skopiować obiekt zamiast mutacji.

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))

Oto wersja, która zamuści się w sposób, w jaki miałeś nadzieję, że będzie działać. Ważna zmiana jest to, że odcinek ma więcej niż jedno przedmioty, które możesz zapytać:

// 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!"
        }
    }
}

Nadal. Jeśli martwisz się o głębokie klonowanie - polecam zaplanować produkcję w taki sposób, w jaki nie musisz mutować lub sklonować wartość:

// 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))
1
Isaac B 22 listopad 2020, 23:33