запросов в последовательном
Ниже addExtra
принимает входные данные comment
и асинхронно добавляет дополнительные поля к комментарию, а все комментарии children
рекурсивно.
const addExtra = async ({ children = [], ...comment }) =>
({ ...comment
, children: await Promise.all (children.map (addExtra))
, extra: await axios.get (...)
})
Чтобы показать это, мы сначала представим поддельную базу данных.Мы можем запросить дополнительные поля комментария по комментарию id
const DB =
{ 1: { a: "one" }
, 2: { a: "two", b: "dos" }
, 4: [ "anything" ]
}
const fetchExtra = async (id) =>
DB [id]
fetchExtra (2)
.then (console.log, console.error)
// { "a": "two"
// , "b": "dos"
// }
Теперь вместо axios.get
мы используем fetchExtra
.Мы можем видеть, что addExtra
работает как задумано, учитывая первый комментарий в качестве ввода
const comments =
[ /* your data */ ]
const addExtra = async ({ children = [], ...comment }) =>
({ ...comment
, children: await Promise.all (children.map (addExtra))
, extra: await fetchExtra (comment.id)
})
addExtra (comments [0])
.then (console.log, console.error)
// { id: 1
// , text: "foo"
// , children:
// [ {id: 2
// , text: "foo-child"
// , children:[]
// , extra: { a: "two", b: "dos" } // <-- added field
// }
// , { id: 3
// , text: "foo-child-2"
// , children:[]
// }
// ]
// , extra: { a: "one" } // <-- added field
// }
Поскольку у вас есть массив комментариев, мы можем использовать map
до addExtra
для каждого
Promise.all (comments .map (addExtra))
.then (console.log, console.error)
// [ { id: 1
// , text: "foo"
// , children:
// [ {id: 2
// , text: "foo-child"
// , children:[]
// , extra: { a: "two", b: "dos" } // <--
// }
// , { id: 3
// , text: "foo-child-2"
// , children:[]
// }
// ]
// , extra: { a: "one" } // <--
// }
// , { id: 4
// , text: "bar"
// , children:[]
// , extra: [ 'anything' ] // <--
// }
// ]
Использование Promise.all
является бременем для пользователя, поэтому было бы неплохо иметь что-то вроде addExtraAll
const addExtraAll = async (comments) =>
Promise.all (comments .map (addExtra))
addExtraAll (comments)
.then (console.log, console.error)
// same output as above
рефакторинг и просветить
Вы заметили дублирование кода?Здравствуйте, взаимная рекурсия ...
const addExtraAll = async (comments) =>
Promise.all (comments .map (addExtra))
const addExtra = async ({ children = [], ...comment }) =>
({ ...comment
<del>, children: await Promise.all (children .map (addExtra))</del>
, children: await addExtraAll (children)
, extra: await fetchExtra (comment.id)
})
addExtra (singleComment) // => Promise
addExtraAll (manyComments) // => Promise
Проверьте результаты в вашем собственном браузере ниже
const addExtraAll = async (comments) =>
Promise.all (comments .map (addExtra))
const addExtra = async ({ children = [], ...comment }) =>
({ ...comment
, children: await addExtraAll (children)
, extra: await fetchExtra (comment.id)
})
const DB =
{ 1: { a: "one" }
, 2: { a: "two", b: "dos" }
, 4: [ "anything" ]
}
const fetchExtra = async (id) =>
DB [id]
const comments =
[ { id: 1
, text: "foo"
, children:
[ {id: 2
, text: "foo-child"
, children:[]
}
, { id: 3
, text: "foo-child-2"
, children:[]
}
]
}
, { id: 4
, text: "bar"
, children:[]
}
]
addExtra (comments [0])
.then (console.log, console.error)
// { id: 1
// , text: "foo"
// , children:
// [ {id: 2
// , text: "foo-child"
// , children:[]
// , extra: { a: "two", b: "dos" } // <-- added field
// }
// , { id: 3
// , text: "foo-child-2"
// , children:[]
// }
// ]
// , extra: { a: "one" } // <-- added field
// }
addExtraAll (comments)
.then (console.log, console.error)
// [ { id: 1
// , text: "foo"
// , children:
// [ {id: 2
// , text: "foo-child"
// , children:[]
// , extra: { a: "two", b: "dos" } // <--
// }
// , { id: 3
// , text: "foo-child-2"
// , children:[]
// }
// ]
// , extra: { a: "one" } // <--
// }
// , { id: 4
// , text: "bar"
// , children:[]
// , extra: [ 'anything' ] // <--
// }
// ]
добавить несколько полей
Выше addExtra
просто и добавляет только одно поле extra
к вашему комментарию.Мы можем добавить любое количество полей
const addExtra = async ({ children = [], ...comment }) =>
({ ...comment
, children: await addExtraAll (children)
, extra: await axios.get (...)
, other: await axios.get (...)
, more: await axios.get (...)
})
результаты слияния
Вместо добавления полей в comment
, также возможно объединить извлеченные данные. Однако вы должны принять некоторые меры предосторожности здесь ...
const addExtra = async ({ children = [], ...comment }) =>
({ ...await fetchExtra (comment.id)
, ...comment
, children: await addExtraAll (children)
})
addExtra (comments [0])
.then (console.log, console.error)
// {
// , a: 1 // <-- extra fields are merged in with the comment
// , id: 1
// , text: "foo"
// , children: [ ... ]
// }
Обратите внимание на порядок вызовов выше.Поскольку сначала мы вызываем ...await
, для выбранных данных невозможно заменить поля в вашем комментарии.Например, если fetchExtra(1)
вернул { a: 1, id: null }
, мы все равно получили бы комментарий { id: 1 ... }
.Если вы хотите, чтобы добавленные поля могли перезаписывать существующие поля в вашем комментарии, вы можете изменить порядок
И, наконец, вы можете сделать несколько слияний, если хотите
const addExtra = async ({ children = [], ...comment }) =>
({ ...await fetchExtra (comment.id)
, ...await fetchMore (comment.id)
, ...await fetchOther (comment.id)
, ...comment
, children: await addExtraAll (children)
})
запросов параллельно
Один из недостатков вышеописанного подхода заключается в том, что запросы на дополнительные поля выполняются в последовательном порядке.
Было бы неплохоесли бы мы могли указать функцию, которая принимает наш комментарий в качестве входных данных и возвращает объект полей, которые мы хотим добавить.На этот раз мы пропускаем ключевые слова await
, чтобы наша функция могла автоматически распараллеливать подзапросы для нас
addFieldsAll
( c => ({ extra: fetchExtra (c.id), other: fetchOther (c.id) })
, comments
)
.then (console.log, console.error)
// [ { id: 1
// , children: [ ... ] // <-- fields added to children recursively
// , extra: ... // <-- added extra field
// , other: ... // <-- added other field
// }
// , ...
// ]
Вот один из способов реализации addFieldsAll
.Также обратите внимание, что из-за упорядочения аргументов Object.assign
для дескриптора возможно *1094* указать поля, которые будут перезаписывать поля во входном комментарии - например, c => ({ id: regenerateId (c.id), ... })
.Как описано выше, это поведение может быть изменено путем изменения порядка аргументов по желанию
const addFieldsAll = async (desc = () => {} , comments = []) =>
Promise.all (comments .map (c => addFields (desc, c)))
const addFields = async (desc = () => {}, { children = [], ...comment}) =>
Object.assign
( comment
, { children: await addFieldsAll (desc, children) }
, ... await Promise.all
( Object .entries (desc (comment))
.map (([ field, p ]) =>
p.then (res => ({ [field]: res })))
)
)