JavaScript:等待递归树完成,其中每个递归级别是一个 API 调用
JavaScript: Await for a recursive tree to finish, where each recursive level is an API call
我正在尝试使用递归 API 调用构建 JSON 树,但我在数据结构的流控制方面遇到问题。如何在 BuildTree 函数堆栈结束之前阻止流程?
这是部分代码。提前致谢。
//FIRST CALL TO RETRIEVE THE ROOT
function callFW() {
d3.json(url, function(data) { //?<----- api call
Tree["uid"] = data.request.uid
Tree["hid"] = data.firmware.meta_data.hid
Tree["size"] = data.firmware.meta_data.size
Tree["children"] = [];
BuildTree(data.firmware.meta_data.included_files,Tree["children"])
//WAIT FOR BUILDTREE
console.log(Tree)
}
}
BuildTree 函数是这样的:
async function BuildTree(included_files, fatherNode){
if( included_files.length > 0){
promises = [];
included_files.forEach(function(item) {
url = endpoint+"file_object/"+item+"?summary=true";
promises.push(axios.get(url));
});
Promise.all(promises).then(function (results) {
results.forEach(function (response) {
var node = {}
node["uid"]= response.data.request.uid
node["hid"]= response.data.file_object.meta_data.hid
node["size"] = response.data.file_object.meta_data.size
node["children"] = []
fatherNode.push(node)
BuildTree(response.data.file_object.meta_data.included_files, node["children"])
});
});
}
}
您无缘无故地使用了 async
关键字,因为您没有在函数内部使用 await
。你不妨使用它:)
async function BuildTree(included_files, fatherNode) {
if (included_files.length > 0) {
let promises = included_files.map( item => {
let url = endpoint + "file_object/" + item + "?summary=true";
return axios.get(url)
});
const results = await Promise.all(promises);
for(let response of results){
var node = {}
node["uid"] = response.data.request.uid
node["hid"] = response.data.file_object.meta_data.hid
node["size"] = response.data.file_object.meta_data.size
node["children"] = []
fatherNode.push(node)
await BuildTree(response.data.file_object.meta_data.included_files, node["children"]);
};
}
};
然后你可以简单地 await BuildTree
:
function callFW() {
d3.json(url, async function(data) {
Tree["uid"] = data.request.uid
Tree["hid"] = data.firmware.meta_data.hid
Tree["size"] = data.firmware.meta_data.size
Tree["children"] = [];
await BuildTree(data.firmware.meta_data.included_files,Tree["children"]);
console.log(Tree)
})
}
我认为让功能更多一些会更简洁self-contained。例如,主函数可以替换
Tree["children"] = [];
await BuildTree(data.firmware.meta_data.included_files,Tree["children"]);
和
Tree["children"] = await BuildTree (data.firmware.meta_data.included_files)
如果我们稍微重组 BuildTree
。为此,我们可以编写 BuildTree
来递归构建树的 children
节点,如下所示:
const endpoint = 'http://my.service/'
const BuildTree = async (files) => Promise .all (
files .map ((name) =>
axios .get (`${endpoint}file_object/${name}?summary=true`)
.then (async response => ({
uid: response.data.request.uid,
hid: response.data.file_object.meta_data.hid,
size: response.data.file_object.meta_data.size,
children: await BuildTree (response.data.file_object.meta_data.included_files)
}))
)
)
BuildTree (['ca26bcfc'])
.then (console .log)
.catch (console .warn)
.as-console-wrapper {max-height: 100% !important; top: 0}
<script>/* Faking axios call and service behind it */ const axios = {get: async (url, uid = url.match(/\/([^?\/]+)\?summary/)[1]) => ({data: {request: {uid}, file_object: {meta_data: {hid: guid(), size: randSize(), included_files: [...Array ((Math .random () * 3 | 0) * (Math .random () * 3 | 0))] .map (guid)}}}})}, guid = () => 'xxxxxxxx' .replace(/x/g, () => ((Math .random () * 16 | 0) .toString (16))), randSize = () => (Math.random() * 1000 | 0) + 1000</script>
(请注意,有一个无趣的虚拟版本 axios
涉及,伪造服务调用,随机提供 hid
和 size
参数并随机返回零到四个 children. 所以每一次 运行 都会给你返回不同的随机数据,有时是一层深,有时是五层或十层甚至更多层,但你可能要 运行 几次才能看到嵌套。 )
这里重要的是如何管理递归调用。它只是 returns 一个项目数组的承诺,所以我们可以插入它来生成 children 就像我们在 main 函数的调用中所做的那样。这会导致更清晰的代码。
我还要做一件事来改进这个功能。它正在使用全局变量 endpoint
。这使得它不纯,更难测试,也更脆弱。相反,我们可以传入它。我更喜欢这个版本:
const BuildTree = async (endpoint, files) => Promise .all (
files .map ((name) =>
axios .get (`${endpoint}file_object/${name}?summary=true`)
.then (async response => ({
uid: response.data.request.uid,
hid: response.data.file_object.meta_data.hid,
size: response.data.file_object.meta_data.size,
children: await BuildTree (endpoint, response.data.file_object.meta_data.included_files)
}))
)
)
BuildTree ('http://my.service/', ['ca26bcfc'])
.then (console .log)
.catch (err => console .log (`Error: ${err}`))
甚至:
const BuildTree = (endpoint) => async (files) => Promise .all (
// ...
children: await BuildTree (endpoint) (response.data.file_object.meta_data.included_files)
// ...
BuildTree ('http://my.service/') (['ca26bcfc'])
// ...
我还推荐一个类似 clean-up 的 main 函数,它不带参数,但使用全局变量 Tree
,再次使它更难测试。但是我对你的环境了解不够,无法提供实用的建议。
我正在尝试使用递归 API 调用构建 JSON 树,但我在数据结构的流控制方面遇到问题。如何在 BuildTree 函数堆栈结束之前阻止流程?
这是部分代码。提前致谢。
//FIRST CALL TO RETRIEVE THE ROOT
function callFW() {
d3.json(url, function(data) { //?<----- api call
Tree["uid"] = data.request.uid
Tree["hid"] = data.firmware.meta_data.hid
Tree["size"] = data.firmware.meta_data.size
Tree["children"] = [];
BuildTree(data.firmware.meta_data.included_files,Tree["children"])
//WAIT FOR BUILDTREE
console.log(Tree)
}
}
BuildTree 函数是这样的:
async function BuildTree(included_files, fatherNode){
if( included_files.length > 0){
promises = [];
included_files.forEach(function(item) {
url = endpoint+"file_object/"+item+"?summary=true";
promises.push(axios.get(url));
});
Promise.all(promises).then(function (results) {
results.forEach(function (response) {
var node = {}
node["uid"]= response.data.request.uid
node["hid"]= response.data.file_object.meta_data.hid
node["size"] = response.data.file_object.meta_data.size
node["children"] = []
fatherNode.push(node)
BuildTree(response.data.file_object.meta_data.included_files, node["children"])
});
});
}
}
您无缘无故地使用了 async
关键字,因为您没有在函数内部使用 await
。你不妨使用它:)
async function BuildTree(included_files, fatherNode) {
if (included_files.length > 0) {
let promises = included_files.map( item => {
let url = endpoint + "file_object/" + item + "?summary=true";
return axios.get(url)
});
const results = await Promise.all(promises);
for(let response of results){
var node = {}
node["uid"] = response.data.request.uid
node["hid"] = response.data.file_object.meta_data.hid
node["size"] = response.data.file_object.meta_data.size
node["children"] = []
fatherNode.push(node)
await BuildTree(response.data.file_object.meta_data.included_files, node["children"]);
};
}
};
然后你可以简单地 await BuildTree
:
function callFW() {
d3.json(url, async function(data) {
Tree["uid"] = data.request.uid
Tree["hid"] = data.firmware.meta_data.hid
Tree["size"] = data.firmware.meta_data.size
Tree["children"] = [];
await BuildTree(data.firmware.meta_data.included_files,Tree["children"]);
console.log(Tree)
})
}
我认为让功能更多一些会更简洁self-contained。例如,主函数可以替换
Tree["children"] = [];
await BuildTree(data.firmware.meta_data.included_files,Tree["children"]);
和
Tree["children"] = await BuildTree (data.firmware.meta_data.included_files)
如果我们稍微重组 BuildTree
。为此,我们可以编写 BuildTree
来递归构建树的 children
节点,如下所示:
const endpoint = 'http://my.service/'
const BuildTree = async (files) => Promise .all (
files .map ((name) =>
axios .get (`${endpoint}file_object/${name}?summary=true`)
.then (async response => ({
uid: response.data.request.uid,
hid: response.data.file_object.meta_data.hid,
size: response.data.file_object.meta_data.size,
children: await BuildTree (response.data.file_object.meta_data.included_files)
}))
)
)
BuildTree (['ca26bcfc'])
.then (console .log)
.catch (console .warn)
.as-console-wrapper {max-height: 100% !important; top: 0}
<script>/* Faking axios call and service behind it */ const axios = {get: async (url, uid = url.match(/\/([^?\/]+)\?summary/)[1]) => ({data: {request: {uid}, file_object: {meta_data: {hid: guid(), size: randSize(), included_files: [...Array ((Math .random () * 3 | 0) * (Math .random () * 3 | 0))] .map (guid)}}}})}, guid = () => 'xxxxxxxx' .replace(/x/g, () => ((Math .random () * 16 | 0) .toString (16))), randSize = () => (Math.random() * 1000 | 0) + 1000</script>
(请注意,有一个无趣的虚拟版本 axios
涉及,伪造服务调用,随机提供 hid
和 size
参数并随机返回零到四个 children. 所以每一次 运行 都会给你返回不同的随机数据,有时是一层深,有时是五层或十层甚至更多层,但你可能要 运行 几次才能看到嵌套。 )
这里重要的是如何管理递归调用。它只是 returns 一个项目数组的承诺,所以我们可以插入它来生成 children 就像我们在 main 函数的调用中所做的那样。这会导致更清晰的代码。
我还要做一件事来改进这个功能。它正在使用全局变量 endpoint
。这使得它不纯,更难测试,也更脆弱。相反,我们可以传入它。我更喜欢这个版本:
const BuildTree = async (endpoint, files) => Promise .all (
files .map ((name) =>
axios .get (`${endpoint}file_object/${name}?summary=true`)
.then (async response => ({
uid: response.data.request.uid,
hid: response.data.file_object.meta_data.hid,
size: response.data.file_object.meta_data.size,
children: await BuildTree (endpoint, response.data.file_object.meta_data.included_files)
}))
)
)
BuildTree ('http://my.service/', ['ca26bcfc'])
.then (console .log)
.catch (err => console .log (`Error: ${err}`))
甚至:
const BuildTree = (endpoint) => async (files) => Promise .all (
// ...
children: await BuildTree (endpoint) (response.data.file_object.meta_data.included_files)
// ...
BuildTree ('http://my.service/') (['ca26bcfc'])
// ...
我还推荐一个类似 clean-up 的 main 函数,它不带参数,但使用全局变量 Tree
,再次使它更难测试。但是我对你的环境了解不够,无法提供实用的建议。