Javascript 递归函数中的承诺

Javascript 递归函数中的承诺,javascript,promise,es6-promise,Javascript,Promise,Es6 Promise,我有以下代码: 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 = [].slic

我有以下代码:

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);
    }
  }
}
功能加载(实验室,el){
返回Promise.all([Util.loadHtml(lab)、Util.loadScript(lab)])
.然后(功能(响应){
解析(响应[0],el,响应[1]);
});
}
函数解析(html、父级、上下文){
var children=[].slice.call(html.childNodes).filter(函数(项){return item.nodeType==1 | | item.nodeType==3;});
对于(变量i=0;i=0){
加载(子标记名.toLowerCase(),父级);
}
否则{
var parsedNode=parseNode(子节点,上下文);
parent.appendChild(parsedNode);
if(child.hasChildNodes())
parse(子级、parsedNode、上下文);
}
}
}
基本上,这就是它应该做的:

  • 在我的app.js中,我调用
    load
    函数,该函数将导入两个文件,一个
    html
    和一个
    js
    ,当这些请求的承诺得到满足时,它将调用名为
    parse
    的函数,该函数将循环到html中,并使用js文件中声明的类解析一些字符串
  • 在循环中,可以找到一些自定义标记,例如
    ,然后,它将尝试加载
    my element.html
    my element.js
    ,并在该html中循环
  • 正如您在代码中看到的,我传递了父级和上下文,因此在完成所有循环之后,“大”父级应该包含所有其他组件
  • 问题

    由于
    load
    函数返回一个承诺,我同步调用它,它立即返回,因此,子项没有放在正确的父项中


    例如,如果我在C#中这样做,或者使用ES7
    async
    await
    关键字,那将非常简单。但是我不知道如何异步调用
    load
    函数。有什么猜测吗?

    我想这就是你弄错的地方:

    if (child.tagName.indexOf('-') >= 0) {
          load(child.tagName.toLowerCase(), parent);
        }
    

    您正在将
    子对象的
    父对象
    传递为
    孙对象的
    父对象
    。您可能需要将
    子项
    作为您可以使用的
    孙子项

    的父项传递。减少以实现此目的:

    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());
    }
    
    然后,当遍历子级时,它将继续链接承诺,每个子级可以独立地加载或解析,直到所有子级都被解析,承诺将从
    parse
    get resolved返回。因此,您现在可以像这样使用它:

    parse(THE PARAM OF ROOT).then(function() {
        // All the big parents's children are now parsed.
        console.log('All done');
    });
    
    编辑:正如建议的那样,这样更好。减少,因为当任何子(孙)失败时,它会立即拒绝。如前所述,我只提供一个指向它的链接,而不是添加
    .all
    版本


    如果函数是异步的,它应该返回一个承诺。总是甚至(或:特别是)在
    中,然后
    回调

    如果您在该循环中产生多个承诺,您可以通过
    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);
    //      ^^^^^^
        }
      }));
    }
    
    例如,如果我在C#中这样做,或者使用ES7 async和await关键字,那将非常简单。但我不知道如何异步调用
    load
    函数

    是的,你真的应该考虑使用那些。或者您可以使用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);
        }
      }
    }
    
    功能加载(实验室,el){
    返回Promise.all([Util.loadHtml(lab)、Util.loadScript(lab)])
    .然后(功能(响应){
    return parse(responses[0],el,responses[1]);//返回此处
    });
    }
    函数解析(html、父级、上下文){
    var children=[].slice.call(html.childNodes).filter(函数(项){return item.nodeType==1 | | item.nodeType==3;});
    对于(变量i=0;i=0){
    返回加载(child.tagName.toLowerCase(),parent);//在此处返回
    }
    否则{
    var parsedNode=parseNode(子节点,上下文);
    parent.appendChild(parsedNode);
    if(child.hasChildNodes())
    parse(子级、parsedNode、上下文);
    }
    }
    }
    
    由于我的解析必须是同步的(因为顺序等原因),我唯一需要做的就是等待
    load
    函数完成,然后再返回
    parse
    ,因此我所做的唯一更改是不直接调用
    load
    函数中的
    parse
    函数,反之亦然,我现在使用
    返回
    ,因为它将等待执行,然后再返回调用方


    还有一件事很管用,而且对我的用例更好:我最终创建了一个自定义元素的克隆,并将其附加到父元素,调用加载函数传递它。这样,我就可以异步加载它的所有子对象,而不会出现不连接到DOM的问题


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

    我看不出它是如何将其放入不正确的父对象中的,因为它是
    异步的。
    。嗯,如果您也对文本节点进行筛选,您不应该在不检查它是否是元素的情况下访问它们的
    .tagName
    。@Bergi事实上,我的代码比这个大得多。我已经简化了我的问题的上下文——我检查节点的类型等:)你应该真正考虑做<代码>返回承诺。所有([承诺,孩子承诺])< /COD>而不是这个<代码>。这样,它会立即拒绝出现错误。@Bergi,谢谢你的提醒,只是在写答案时忘记了
    。所有的
    !代码中没有“逻辑”错误。事实上,如果我没有使用任何自定义标记,它就像一个符咒,只有当我需要加载其他html时才会出现问题