递归函数中的承诺
Promises inside a recursive function
我有以下代码:
function load(lab, el) {
return Promise.all([Util.loadHtml(lab), Util.loadScript(lab)])
.then(function(responses) {
parse(responses[0], el, responses[1]);
});
}
function parse(html, parent, context) {
var children = [].slice.call(html.childNodes).filter(function (item) { return item.nodeType === 1 || item.nodeType === 3; });
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.tagName.indexOf('-') >= 0) {
load(child.tagName.toLowerCase(), parent);
}
else {
var parsedNode = parseNode(child, context);
parent.appendChild(parsedNode);
if (child.hasChildNodes())
parse(child, parsedNode, context);
}
}
}
基本上,这就是它应该做的:
- 在我的 app.js 中,我调用了
load
函数,它将导入两个文件,一个 html
和一个 js
,当这些请求的承诺是fullfilled,它调用一个名为 parse
的函数,该函数将循环到 HTML 并将使用 JS 文件中声明的 class 解析一些字符串。
- 在循环里面,可以找到一些自定义的标签,比如
<my-element>
,然后,它会尝试加载my-element.html
和my-element.js
,它会在里面循环HTML 也是。
- 正如您在代码中看到的那样,我传递了 parent 和上下文,因此在完成所有循环之后,'big' parent 应该包含所有其他组件它。
问题
由于 load
函数 returns 是一个承诺,并且我正在同步调用它,所以它立即 returns,因此 children 不是放置在正确的 parents.
中
如果我在 C# 中这样做,例如,或者使用 ES7 async
和 await
关键字,那将非常容易。但我不知道如何异步调用 load
函数。有什么猜测吗?
我认为这是你弄错的地方:
if (child.tagName.indexOf('-') >= 0) {
load(child.tagName.toLowerCase(), parent);
}
您正在将 child
对象的 parent
传递为 grandchild
对象的 parent
。您可能需要将 child
作为 grandchild
.
的父级传递
您可以使用 .reduce 来实现:
function load(lab, el) {
return Promise.all([Util.loadHtml(lab), Util.loadScript(lab)])
.then(function(responses) {
return parse(responses[0], el, responses[1]);
});
}
function parse(html, parent, context) {
var children = [].slice.call(html.childNodes).filter(function (item) { return item.nodeType === 1 || item.nodeType === 3; });
// What this return is a promise chained through all of its children
// So until all children get resolved, this won't get resolved.
return children.reduce(function(promise, child, idx) {
var childPromise = null;
if (child.tagName.indexOf('-') >= 0) {
// Start to load the contents, until childPromise is resolved, the final
// can't be resolved.
childPromise = load(child.tagName.toLowerCase(), parent);
} else {
var parsedNode = parseNode(child, context);
parent.appendChild(parsedNode);
// If it has child, also make it return a promise which will be resolved
// when child's all children parsed.
if (child.hasChildNodes()) {
childPromise = parse(child, parsedNode, context);
}
}
// It's either null, which means it'll be resolved immediately,
// or a promise, which will wait until its childs are processed.
return promise.then(function() {
return childPromise;
});
}, Promise.resolve());
}
然后,当遍历子项时,它会继续链接承诺,每个子项都可以独立加载或解析,直到所有子项都已解析,承诺 return 来自 parse
得到解决。所以你现在可以像这样使用它:
parse(THE PARAM OF ROOT).then(function() {
// All the big parents's children are now parsed.
console.log('All done');
});
已编辑:由于发布了 Bergi suggests, Promise.all is better then .reduce, as it'll reject immediately when any of the children(grandchildren) fails. And as ,我将只给它一个 link,而不是添加 .all
版本。
而且 JavaScript Promises#chaining 也可能对您有所帮助。
如果一个函数是异步的,它应该return一个承诺。总是。甚至(或:特别是)在 then
回调中。
如果您在该循环中产生多个承诺,您可以通过 Promise.all
:
等待它们
function load(lab, el) {
return Promise.all([Util.loadHtml(lab), Util.loadScript(lab)])
.then(function(responses) {
return parse(responses[0], el, responses[1]);
// ^^^^^^
});
}
function parse(html, parent, context) {
var children = [].slice.call(html.childNodes).filter(function (item) { return item.nodeType === 1 || item.nodeType === 3; });
return Promise.all(children.map(function(child, i) {
//^^^^^^^^^^^^^^^^^^
if (child.tagName.indexOf('-') >= 0) {
return load(child.tagName.toLowerCase(), parent);
// ^^^^^^
} else {
var parsedNode = parseNode(child, context);
parent.appendChild(parsedNode);
if (child.hasChildNodes())
return parse(child, parsedNode, context);
// ^^^^^^
}
}));
}
If I was doing that in C#, for example, or with the ES7 async and await keywords, that would be pretty easy. But I have no idea how can I call that load
function asynchronously
是的,你真的应该考虑使用那些。或者您可以使用 ES6 生成器函数和运行器(由许多流行的 promise 库提供)来模拟它们。但无论如何你都在使用转译器,对吧?
写 load
对他们来说会很容易:
async function load(lab, el) {
var responses = await Promise.all([Util.loadHtml(lab), Util.loadScript(lab)]);
return parse(responses[0], el, responses[1]);
}
最后,比我想象的要简单:
function load(lab, el) {
return Promise.all([Util.loadHtml(lab), Util.loadScript(lab)])
.then(function(responses) {
return parse(responses[0], el, responses[1]); // return here
});
}
function parse(html, parent, context) {
var children = [].slice.call(html.childNodes).filter(function (item) { return item.nodeType === 1 || item.nodeType === 3; });
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.tagName.indexOf('-') >= 0) {
return load(child.tagName.toLowerCase(), parent); // return here
}
else {
var parsedNode = parseNode(child, context);
parent.appendChild(parsedNode);
if (child.hasChildNodes())
parse(child, parsedNode, context);
}
}
}
由于我的解析必须是同步的(因为顺序等),我唯一需要做的就是等待 load
函数完成后再返回 parse
,唯一需要做的就是我改变的是,而不是直接调用 load
中的 parse
函数,反之亦然,我现在使用 return
,从那以后它将等待执行,然后回到来电者那里。
其他有用的东西,它对我的用例来说甚至更好:我最终创建了自定义元素的克隆,并将其附加到父元素,调用传递它的加载函数。这样做,我可以异步加载它的所有子项,而不会出现未附加到 DOM.
的问题
执行速度更快,可读性更好!
我有以下代码:
function load(lab, el) {
return Promise.all([Util.loadHtml(lab), Util.loadScript(lab)])
.then(function(responses) {
parse(responses[0], el, responses[1]);
});
}
function parse(html, parent, context) {
var children = [].slice.call(html.childNodes).filter(function (item) { return item.nodeType === 1 || item.nodeType === 3; });
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.tagName.indexOf('-') >= 0) {
load(child.tagName.toLowerCase(), parent);
}
else {
var parsedNode = parseNode(child, context);
parent.appendChild(parsedNode);
if (child.hasChildNodes())
parse(child, parsedNode, context);
}
}
}
基本上,这就是它应该做的:
- 在我的 app.js 中,我调用了
load
函数,它将导入两个文件,一个html
和一个js
,当这些请求的承诺是fullfilled,它调用一个名为parse
的函数,该函数将循环到 HTML 并将使用 JS 文件中声明的 class 解析一些字符串。 - 在循环里面,可以找到一些自定义的标签,比如
<my-element>
,然后,它会尝试加载my-element.html
和my-element.js
,它会在里面循环HTML 也是。 - 正如您在代码中看到的那样,我传递了 parent 和上下文,因此在完成所有循环之后,'big' parent 应该包含所有其他组件它。
问题
由于 load
函数 returns 是一个承诺,并且我正在同步调用它,所以它立即 returns,因此 children 不是放置在正确的 parents.
如果我在 C# 中这样做,例如,或者使用 ES7 async
和 await
关键字,那将非常容易。但我不知道如何异步调用 load
函数。有什么猜测吗?
我认为这是你弄错的地方:
if (child.tagName.indexOf('-') >= 0) {
load(child.tagName.toLowerCase(), parent);
}
您正在将 child
对象的 parent
传递为 grandchild
对象的 parent
。您可能需要将 child
作为 grandchild
.
您可以使用 .reduce 来实现:
function load(lab, el) {
return Promise.all([Util.loadHtml(lab), Util.loadScript(lab)])
.then(function(responses) {
return parse(responses[0], el, responses[1]);
});
}
function parse(html, parent, context) {
var children = [].slice.call(html.childNodes).filter(function (item) { return item.nodeType === 1 || item.nodeType === 3; });
// What this return is a promise chained through all of its children
// So until all children get resolved, this won't get resolved.
return children.reduce(function(promise, child, idx) {
var childPromise = null;
if (child.tagName.indexOf('-') >= 0) {
// Start to load the contents, until childPromise is resolved, the final
// can't be resolved.
childPromise = load(child.tagName.toLowerCase(), parent);
} else {
var parsedNode = parseNode(child, context);
parent.appendChild(parsedNode);
// If it has child, also make it return a promise which will be resolved
// when child's all children parsed.
if (child.hasChildNodes()) {
childPromise = parse(child, parsedNode, context);
}
}
// It's either null, which means it'll be resolved immediately,
// or a promise, which will wait until its childs are processed.
return promise.then(function() {
return childPromise;
});
}, Promise.resolve());
}
然后,当遍历子项时,它会继续链接承诺,每个子项都可以独立加载或解析,直到所有子项都已解析,承诺 return 来自 parse
得到解决。所以你现在可以像这样使用它:
parse(THE PARAM OF ROOT).then(function() {
// All the big parents's children are now parsed.
console.log('All done');
});
已编辑:由于发布了 Bergi suggests, Promise.all is better then .reduce, as it'll reject immediately when any of the children(grandchildren) fails. And as .all
版本。
而且 JavaScript Promises#chaining 也可能对您有所帮助。
如果一个函数是异步的,它应该return一个承诺。总是。甚至(或:特别是)在 then
回调中。
如果您在该循环中产生多个承诺,您可以通过 Promise.all
:
function load(lab, el) {
return Promise.all([Util.loadHtml(lab), Util.loadScript(lab)])
.then(function(responses) {
return parse(responses[0], el, responses[1]);
// ^^^^^^
});
}
function parse(html, parent, context) {
var children = [].slice.call(html.childNodes).filter(function (item) { return item.nodeType === 1 || item.nodeType === 3; });
return Promise.all(children.map(function(child, i) {
//^^^^^^^^^^^^^^^^^^
if (child.tagName.indexOf('-') >= 0) {
return load(child.tagName.toLowerCase(), parent);
// ^^^^^^
} else {
var parsedNode = parseNode(child, context);
parent.appendChild(parsedNode);
if (child.hasChildNodes())
return parse(child, parsedNode, context);
// ^^^^^^
}
}));
}
If I was doing that in C#, for example, or with the ES7 async and await keywords, that would be pretty easy. But I have no idea how can I call that
load
function asynchronously
是的,你真的应该考虑使用那些。或者您可以使用 ES6 生成器函数和运行器(由许多流行的 promise 库提供)来模拟它们。但无论如何你都在使用转译器,对吧?
写 load
对他们来说会很容易:
async function load(lab, el) {
var responses = await Promise.all([Util.loadHtml(lab), Util.loadScript(lab)]);
return parse(responses[0], el, responses[1]);
}
最后,比我想象的要简单:
function load(lab, el) {
return Promise.all([Util.loadHtml(lab), Util.loadScript(lab)])
.then(function(responses) {
return parse(responses[0], el, responses[1]); // return here
});
}
function parse(html, parent, context) {
var children = [].slice.call(html.childNodes).filter(function (item) { return item.nodeType === 1 || item.nodeType === 3; });
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.tagName.indexOf('-') >= 0) {
return load(child.tagName.toLowerCase(), parent); // return here
}
else {
var parsedNode = parseNode(child, context);
parent.appendChild(parsedNode);
if (child.hasChildNodes())
parse(child, parsedNode, context);
}
}
}
由于我的解析必须是同步的(因为顺序等),我唯一需要做的就是等待 load
函数完成后再返回 parse
,唯一需要做的就是我改变的是,而不是直接调用 load
中的 parse
函数,反之亦然,我现在使用 return
,从那以后它将等待执行,然后回到来电者那里。
其他有用的东西,它对我的用例来说甚至更好:我最终创建了自定义元素的克隆,并将其附加到父元素,调用传递它的加载函数。这样做,我可以异步加载它的所有子项,而不会出现未附加到 DOM.
的问题执行速度更快,可读性更好!