【问题标题】:calling recursive callback within function returning promise在返回承诺的函数中调用递归回调
【发布时间】:2018-07-11 21:10:54
【问题描述】:

我有一个将节点附加到 dom 的函数,如果节点是具有“src”属性的脚本标签,它将为“load”事件设置一个事件监听器,并且回调是相同的函数,所以当 js标签将完成执行相同的函数将被再次调用并继续在它左边的位置添加节点(使用文档片段) 伪代码示例:

function appender(){// i'm calling the function with bind so 'this' is my object
while(documentfragment.children.length > 0){
if(scriptwithsrc){
node.addeventlistener("load",appender.bind(this));
parentnode.appendchild(node);
return;
}
//if its not a script tag than just append the node
parentnode.appendchild(node);

return Promise.resolve();

现在我有了另一个函数,即执行这个函数的“主”函数。 在appender func 完成后,我正在使用“then”继续执行。 伪代码示例:

let appenderBinder = appender.bind(Myobject);
Myobject.fragment = fragment1;
appenderBinder().then(() => {
//first execution for fragment1 is over lets continue to fragment2
//we call it again but change our object before that
Myobject.fragment = fragment2;
appenderBinder().then(() => {
//some more code
})})

问题在于,如果片段包含带有 src 的脚本标记,则该函数只会返回;并且不返回承诺,所以我得到了

"Cannot read property 'then' of undefined",

但是我需要使用 return 来退出函数并等待回调再次执行它并继续从同一个位置追加,并且只有在它完成追加每个节点之后,promise 才会返回并且 ".then ()”应该被执行。 有什么想法吗?

【问题讨论】:

  • 我认为您在这里所做的不是处理此问题的合适方法。它是可以修复的,但即使修复它也不是处理这个问题的正确方法。要解决此问题,您还需要在有脚本时返回一个承诺,但您需要返回和未解决的承诺,然后一旦加载事件触发,它就会“解决”。在此处阅读有关如何执行此操作的更多信息:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • @Dellirium 首先非常感谢您的回复,确实我可以创建一个promise并使用promise.all在所有promise返回并且函数完成附加所有内容时返回,但是当遇到脚本标签时我在继续追加之前需要等待加载事件触发,我只是看不到如何实现这一点,因为我需要等待前一个承诺在继续之前解决,而不是在所有承诺解决后返回 promise.all 到 main函数
  • 让我确认我理解正确,假设您有一个处理片段 A、B、C 和 D 的承诺链,假设片段 B 有一个脚本标签。这个标签被加载,加载后你想简单地继续到 C 和 D 上,或者你想做其他事情,比如,idk 加载一个不在初始链中的 X?
  • @Dellirium 让我详细解释一下:假设我有一个有 10 个节点的片段,所以“附加器”开始附加每个节点(我们需要保持顺序,这对于 js 标签至关重要) 我们遇到一个 js 标签,所以我们绑定到 load 事件,此时我们需要等待 js 执行和 load 事件触发,这样我们才能继续附加节点
  • 我在下面添加了一个带有解释的答案,但是,请注意您在代码中使用了 while 并且没有关闭帖子中的 while 标记。正是由于这个原因,我没有为“引用”代码的while 部分而烦恼,但从逻辑上讲,它必须在执行常规return Promise.all() 之前关闭它的块,我认为这是这里的目标

标签: javascript html asynchronous promise


【解决方案1】:

取自 mozzila 的 MDN:

Promise 对象是使用 new 关键字及其构造函数创建的。此构造函数将一个函数作为其参数,称为“执行器函数”。这个函数应该有两个函数作为参数。当异步任务成功完成并将任务结果作为值返回时,将调用这些函数中的第一个 (resolve)。第二个(reject)在任务失败时调用,并返回失败的原因,通常是一个错误对象。

附上此代码示例:

const myFirstPromise = new Promise((resolve, reject) => {
// do something asynchronous which eventually calls either:
//
//   resolve(someValue); // fulfilled
// or
//   reject("failure reason"); // rejected
});

那么这如何适用于您的问题:

如果遇到脚本标记,您想中断承诺链,在这种情况下,您想先等待它加载,然后再继续沿着链向下走。为此,您需要稍微更改一下代码。

if(scriptwithsrc){
  node.addeventlistener("load",appender.bind(this));
  parentnode.appendchild(node);
  return; //This line here is the problem
}

上面的代码取自您的帖子并且正在同步运行,而您正在做的是,您没有返回任何值。由于您不返回承诺,因此您的函数不能在 then-able 承诺链中使用。你需要做的是:

if(scriptwithsrc){
  let loadingDone = new Promise(function(res, rej){
    node.addEventListener("load", res)
    parentnode.appendchild(node)
  }
  return loadingDone; // This line returns the promise as usual
}
else{
  parentnode.appendchild(node)
  return Promise.resolve() //And so does this line
}

一旦脚本标签加载,它将触发res 函数,该函数将解决承诺并且您的链将继续。在这种特殊情况下,mozzila 代码示例的 something asynchronous 部分指的是脚本标签的加载。

编辑:由于最初的帖子不是很清楚,后来在 cmets 中得到了澄清,因此链需要从它在当前片段中停止的位置继续,因此根据请求添加了一个工作示例来反映这一点.

工作示例:

function appender(arg) {
    let i;
    for (i = 0; i < arg.length; i++) {
        if (arg[i].script) {
            let chainedPromise;
            let pauseProm = new Promise(function (res, rej) {
                setTimeout(function () {
                    console.log('Appended element: ', arg[i].name);
                    res();
                }, 2000); //to mimic script load;
            });
            let j;
            let newChain = [];
            for (j = i + 1; j < arg.length; j++) {
                newChain.push(arg[j]);
            }
            chainedPromise = pauseProm.then(function () {
                return appender(newChain)
            });
            return chainedPromise;
        } else
            console.log('Appended element: ', arg[i].name);
    }
    return Promise.resolve();
}

function Main() {
    appender([
        {name: '1', script: false},
        {name: '2', script: false},
        {name: '3', script: true}
    ]).then(function () {
        appender([
            {name: '4', script: false},
            {name: '5', script: false},
            {name: '6', script: true},
            {name: '7', script: true}
        ]).then(function () {
            appender([
                {name: '8', script: false},
                {name: '9', script: true},
                {name: '10', script: false},
                {name: '11', script: true},
                {name: '12', script: true}
            ]).then(function () {
                console.log('done')
            });
        });
    });
}
Main();

【讨论】:

  • 这不是我所需要的,因为正如您在我的“main”函数中看到的那样,当整个 func 操作完成时,我正在使用“then”执行更多代码,我的意思是附加所有内容.在您的示例中,我返回了一个承诺,该承诺将在脚本标记触发加载事件时解决,以及脚本标记之后的所有其他节点呢?当所有承诺都解决时,我可以使用 promise.all 来解决,但我仍然需要维护订单并等待每个承诺解决,然后再继续下一个
  • 链会继续,就像没有找到脚本标签一样,唯一发生的事情是代码执行中的pause。主函数中的 .then 仍然会适当地触发,因为承诺链会继续。
  • 首先在我的示例中,“return promise.resolve”在 while 循环之外,抱歉这只是一个示例。如果我将使用promise.all 并且只是将所有promise 推送到一个数组并在它们完成主函数中的“then”时解析将执行,但这不是我所需要的,因为可以说我遇到了一个脚本标签我会将承诺推送到我们的数组并继续循环,而不是脚本标签之后的节点很可能会在脚本标签事件触发之前被附加,这不好我需要维护订单
  • 我知道当所有的 promise 都解决后,main 会继续执行“then”代码,这不是我的问题。我的问题是我需要维护节点的顺序,所以我真正需要的是等待每个承诺解决,然后再推送下一个承诺,并且在每个承诺解决之后返回 promise.all 并继续执行 main 中的“then”函数
  • 好的,所以你的问题不清楚,让我编辑我的答案以反映这一点
【解决方案2】:

只有在它完成附加每个节点之后,promise 才会返回

不,您需要立即创建并返回承诺。当您完成附加所有节点时,应履行承诺。所以你会写

function appender() {
  if (documentfragment.children.length > 0){
    if (scriptwithsrc) {
      const promise = new Promise(resolve => {
//                    ^^^^^^^^^^^
        node.addeventlistener("load", resolve);
//                                    ^^^^^^^
        parentnode.appendchild(node);
      });
      return promise.then(appender.bind(this));
//           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    } else { // its not a script tag then just append the node
      parentnode.appendchild(node);
      return appender.call(this); // recurse
    }
  } else {
    return Promise.resolve();
  }
}

【讨论】:

  • 它确实保持顺序对吗?并且 main 函数中的“then”只有在 appender() 完成附加所有内容时才会执行?
  • @avidahan 是的,then() 为回调结果返回另一个承诺
  • 简洁优雅,完美!
  • 如何避免为单个节点调用 appender 两次?就像,如果你遇到一个脚本标签,你会返回promise.then(appender.bind(this)),但由于这是在中途迭代的东西,它只会从开始开始,一旦它再次到达相同的脚本标签,就会导致无限循环,我错过了什么?
  • @Dellirium 为什么要从头开始?您的伪代码没有显示您从哪里获得node,但我假设您将从documentfragment 中删除它,以便在递归评估期间它被清空。这在脚本和非脚本案例之间应该没有什么不同。
猜你喜欢
  • 2020-01-24
  • 2023-03-28
  • 2019-05-25
  • 1970-01-01
  • 2016-06-15
  • 2017-05-09
  • 2016-10-24
  • 2018-02-24
  • 2018-10-11
相关资源
最近更新 更多