Javascript 您如何知道一个无限长的承诺链何时完全完成?

Javascript 您如何知道一个无限长的承诺链何时完全完成?,javascript,asynchronous,promise,Javascript,Asynchronous,Promise,我试图使用承诺来强制序列化一系列Ajax调用。每次用户按下按钮时,都会进行一次Ajax调用。我可以成功序列化如下操作: // sample async function // real-world this is an Ajax call function delay(val) { log("start: ", val); return new Promise(function(resolve) { setTimeout(function() {

我试图使用承诺来强制序列化一系列Ajax调用。每次用户按下按钮时,都会进行一次Ajax调用。我可以成功序列化如下操作:

// sample async function
// real-world this is an Ajax call
function delay(val) {
    log("start: ", val);
    return new Promise(function(resolve)  {
        setTimeout(function() {
            log("end: ", val); 
            resolve();
        }, 500);
    });
}

// initialize p to a resolved promise
var p = Promise.resolve();
var v = 1;

// each click adds a new task to 
// the serially executed queue
$("#run").click(function() {
    // How to detect here that there are no other unresolved .then()
    // handlers on the current value of p?
    p = p.then(function() {
        return delay(v++);
    });
});
工作演示:

但是,这构建了一个潜在的永无止境的承诺链,因为存储最后一个承诺的变量
p
从未被清除。每一项新的行动都是以先前的承诺为基础的。因此,我在想,为了实现良好的内存管理,我应该能够检测何时不再有
。然后()
处理程序在
p
的当前值上运行,然后我可以重置
p
的值,确保前一个承诺处理程序链在闭包中可能持有的任何对象都有资格进行垃圾收集


所以,我想知道,在给定的
.then()
处理程序中,如何知道在这个链中没有更多的
.then()
处理程序被调用,因此,我可以只执行
p=Promise.resolve()
重置
p
并释放之前的承诺链,而不是不断添加到承诺链上。

您可以将承诺推送到数组中并使用
承诺。所有

var p = Promise.resolve, 
   promiseArray = [], 
   allFinishedPromise;

function cleanup(promise, resolvedValue) {
    // You have to do this funkiness to check if more promises
    // were pushed since you registered the callback, though.
    var wereMorePromisesPushed = allFinishedPromise !== promise;
    if (!wereMorePromisesPushed) {
        // do cleanup
        promiseArray.splice(0, promiseArray.length);
        p = Promise.resolve(); // reset promise
    }
}

$("#run").click(function() {
    p = p.then(function() {
        return delay(v++);
    });
    promiseArray.push(p)
    allFinishedPromise = Promise.all(promiseArray);
    allFinishedPromise.then(cleanup.bind(null, allFinishedPromise));
});
或者,因为您知道它们是按顺序执行的,所以可以让每个完成回调从数组中删除该承诺,并在数组为空时重置该承诺

var p = Promise.resolve(), 
    promiseArray = [];

function onPromiseComplete() {
    promiseArray.shift();
    if (!promiseArray.length) {
        p = Promise.resolve();
    }
}

$("#run").click(function() {
    p = p.then(function() {
        onPromiseComplete();
        return delay(v++);
    });
    promiseArray.push(p);
});
编辑:但是,如果数组可能变得很长,则应使用第一个选项b/c将数组移位为O(N)

编辑:如您所述,没有理由保留数组。柜台就行了

var p = Promise.resolve(), 
    promiseCounter = 0;

function onPromiseComplete() {
    promiseCounter--;
    if (!promiseCounter) {
        p = Promise.resolve();
    }
}

$("#run").click(function() {
    p = p.then(function() {
        onPromiseComplete();
        return delay(v++);
    });
    promiseCounter++;
});

我试图检查我们是否可以在代码中看到承诺的状态,很明显这只能从控制台中看到,而不能从代码中看到,因此我使用了一个标志来监视状态,不确定是否存在漏洞:

  var p
    , v = 1
    , promiseFulfilled = true;



  function addPromise() {
    if(!p || promiseFulfilled){
      console.log('reseting promise...');
      p = Promise.resolve();
    }
    p = p.then(function() {
        promiseFulfilled = false;
        return delay(v++);
    }).then(function(){
      promiseFulfilled = true;
    });
  }
无法检测何时没有添加更多处理程序。 事实上,这是一个无法确定的问题。不难看出停机(或
Atm
问题)的减少。如果您愿意,我可以添加一个正式的缩减,但在handwavey中:给定一个输入程序,将承诺放在第一行,并在每次
返回
抛出
时链接到它-假设我们有一个程序可以解决您在这个问题中描述的问题-将其应用到输入问题-我们现在知道它是否永远运行或不解决停止问题。也就是说,你的问题至少和停顿问题一样难

您可以检测承诺何时“解决”,并在新的承诺上更新它。 这在“last”或“flatMap”中很常见。一个很好的用例是自动完成搜索,您只需要最新的结果。这是Domenic的一个实现 ():

我修改了Domenic的代码,并针对您的问题对其进行了记录

您不能安全地对此进行优化 Sane promise实现不遵守“链上”的承诺,因此将其设置为
promise.resolve()
将不会节省内存。如果承诺不这样做,则是内存泄漏,您应该针对它提交一个bug

…这样我就可以重置
p
的值,确保前一个承诺链处理程序在闭包中可能持有的任何对象都有资格进行垃圾收集

不需要。已执行的承诺处理程序(当承诺已解决时)不再需要,并且隐式符合垃圾收集的条件。已解决的承诺只保留解决值

你不需要对承诺(异步值)进行“良好的内存管理”,你的承诺库自己会处理好的。它必须自动“释放以前的承诺链”,如果没有,那就是一个bug。你的模式工作得非常好


您如何知道承诺链何时完成

我将采用一种纯粹的递归方法:

function extendedChain(p, stream, action) {
     // chains a new action to p on every stream event
     // until the chain ends before the next event comes
     // resolves with the result of the chain and the advanced stream
     return Promise.race([
         p.then(res => ({res}) ), // wrap in object to distinguish from event
         stream                   // a promise that resolves with a .next promise
     ]).then(({next, res}) =>
         next
           ? extendedChain(p.then(action), next, action) // a stream event happened first
           : {res, next:stream};                         // the chain fulfilled first
     );
}
function rec(stream, action, partDone) {
    return stream.then(({next}) =>
        extendedChain(action(), next, action).then(({res, next}) => {
            partDone(res);
            return rec(next, action, partDone);
        });
    );
}

var v = 1;
rec(getEvents($("#run"), "click"), () => delay(v++), res => {
    console.log("all current done, none waiting");
    console.log("last result", res);
}); // forever
使用事件流的助手函数,如

function getEvents(emitter, name) {
    var next;
    function get() {
        return new Promise((res) => {
            next = res;
        });
    }
    emitter.on(name, function() {
        next({next: get()});
    });
    return get();
}
()

有人告诉我,“良好”的承诺实现不会导致从无限增长的承诺链中积累内存。但是,显然没有标准要求或描述这一点(良好的编程实践除外)我们有很多新手承诺的实现,所以我还没有决定依赖这种良好的行为是否明智

我多年的编码经验表明,当实现是新的时,缺乏事实证明所有实现都以某种方式运行,并且没有规范规定它们应该以这种方式运行,那么明智的做法可能是将代码编写为“安全的”事实上,仅仅围绕一个不确定的行为编写代码通常比测试所有相关的实现以了解它们的行为方式要少

在这种情况下,我的代码实现在这方面似乎是“安全的”。它只是为每个
.then()
处理程序保存全局last promise变量的一个本地副本,然后()
handler运行,如果全局promise变量仍然具有相同的值,则我的代码没有将更多项目链接到该变量上,因此这必须是当前最后一个
。then()
处理程序。它似乎在以下情况下工作:


第二种方法实际上不需要将承诺存储在数组中,是吗?因为您所看到的只是数组达到零长度时的情况?它不能只使用递增/递减计数器吗(我曾考虑过使用递增/递减计数器,但希望得到保证为我保持计数,因为他们通常都是这样做的)?你的第一个选项让我在试图弄清楚它是如何工作或是否工作时感到头疼。我还没有弄清楚这个选项。如果有其他
。那么()
处理程序(除了我控制的链接到
function getEvents(emitter, name) {
    var next;
    function get() {
        return new Promise((res) => {
            next = res;
        });
    }
    emitter.on(name, function() {
        next({next: get()});
    });
    return get();
}
// sample async function
// real-world this is an Ajax call
function delay(val) {
    log("start: ", val);
    return new Promise(function(resolve)  {
        setTimeout(function() {
            log("end: ", val); 
            resolve();
        }, 500);
    });
}

// initialize p to a resolved promise
var p = Promise.resolve();
var v = 1;

// each click adds a new task to 
// the serially executed queue
$("#run").click(function() {
    var origP = p = p.then(function() {
        return delay(v++);
    }).then(function() {
        if (p === origP) {
            // no more are chained by my code
            log("no more chained - resetting promise head");
            // set fresh promise head so no chance of GC leaks
            // on prior promises
            p = Promise.resolve();
            v = 1;
        }
        // clear promise reference in case this closure is leaked
        origP = null;
    }, function() {
        origP = null;
    });
});