递归函数中的承诺

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

基本上,这就是它应该做的:

  1. 在我的 app.js 中,我调用了 load 函数,它将导入两个文件,一个 html 和一个 js,当这些请求的承诺是fullfilled,它调用一个名为 parse 的函数,该函数将循环到 HTML 并将使用 JS 文件中声明的 class 解析一些字符串。
  2. 在循环里面,可以找到一些自定义的标签,比如<my-element>,然后,它会尝试加载my-element.htmlmy-element.js,它会在里面循环HTML 也是。
  3. 正如您在代码中看到的那样,我传递了 parent 和上下文,因此在完成所有循环之后,'big' parent 应该包含所有其他组件它。

问题

由于 load 函数 returns 是一个承诺,并且我正在同步调用它,所以它立即 returns,因此 children 不是放置在正确的 parents.

如果我在 C# 中这样做,例如,或者使用 ES7 asyncawait 关键字,那将非常容易。但我不知道如何异步调用 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.

的问题

执行速度更快,可读性更好!