循环中的异步回调

Asynchronous callbacks in a loop

我有一个变量 oldBindings,它记录了 Excel table 的所有现有绑定。我已经基于 oldBindings 构建了 BindingDataChanged 个监听器。所以当 newBindings 出现时,我需要删除所有链接到 oldBindings 的旧监听器并添加基于 newBindings 的新监听器。目前,我已经编写了以下代码:

var oldBindings = ["myBind1", "myBind2"]; // can be updated by other functions

function updateEventHandlers(newBindings) {
    removeEventHandlers(oldBindings, function () {
        addEventHandlers(newBindings)
    })
}

function removeEventHandlers(oldBindings, cb) {
    for (var i = 0; i < oldBindings.length; i++) {
        Office.select("binding#"+oldBindings[i]).removeHandlerAsync(Office.EventType.BindingDataChanged, function (asyncResult) {
            Office.context.document.bindings.releaseByIdAsync(oldBindings[i], function () {});
        });
    }
    cb()
}

由于 removeHandlerAsyncreleaseByIdAsync 是用 callback 而不是 promise 构建的,我需要用 callback 来组织整个代码。有两件事我不确定:

1) 在 removeEventHandlers 中,cb() 会在移除所有侦听器后始终执行吗?我如何确保?

2) 我是否必须将 addEventHandlers 作为 removeEventHandlerscallback 来确保它们的执行顺序?

1) in removeEventHandlers, will cb() ALWAYS be executed after the removal of all the listeners?

没有。它将在删除 启动 之后调用。但是如果移除是异步的,它可能会在移除完成之前被调用。

2) Do I have to make addEventHandlers as a callback of removeEventHandlers to ensure their execution order?

是的,但不是你的方式。你拥有的方式就像在做

removeEventHandlers();
addEventHandlers();

因为您在 removeEventHandlers 结束时调用了 cb,没有等待任何事情完成。

As removeHandlerAsync and releaseByIdAsync are built with callback rather than promise, I need to organise the whole code with callback.

或者您可以为自己提供它们的 Promise 版本。稍后会详细介绍。

使用非 Promise 回调方法,以确保您在完成所有工作后从 removeEventHandlers 调用 cb,请记住您期望的回调次数并等待直到获得那么多在调用 cb:

之前
var oldBindings = ["myBind1", "myBind2"]; // can be updated by other functions

function updateEventHandlers(newBindings) {
    removeEventHandlers(oldBindings, function() {
        addEventHandlers(newBindings);
    });
}

function removeEventHandlers(oldBindings, cb) {
    var waitingFor = oldBindings.length;
    for (var i = 0; i < oldBindings.length; i++) {
        Office.select("binding#"+oldBindings[i]).removeHandlerAsync(Office.EventType.BindingDataChanged, function (asyncResult) {
            Office.context.document.bindings.releaseByIdAsync(oldBindings[i], function () {
                if (--waitingFor == 0) {
                    cb();
                }
            });
        });
    }
}

但是任何时候你有一个回调系统,你可以承诺它:

function removeHandlerPromise(obj, eventType) {
    return new Promise(function(resolve, reject) {
        obj.removeHandlerAsync(eventType, function(asyncResult) {
            if (asyncResult.status == Office.AsyncResultStatus.Failed) {
                reject(asyncResult.error);
            } else {
                resolve(asyncResult.value);
            }
        });
    });
}

function releaseByIdPromise(obj, value) {
    return new Promise(function(resolve, reject) {
        obj.releaseByIdAsync(value, function(asyncResult) {
            if (asyncResult.status == Office.AsyncResultStatus.Failed) {
                reject(asyncResult.error);
            } else {
                resolve(asyncResult.value);
            }
        });
    });
}

那么你可以这样做:

var oldBindings = ["myBind1", "myBind2"]; // can be updated by other functions

function updateEventHandlers(newBindings) {
    removeEventHandlers(oldBindings).then(function() {
        addEventHandlers(newBindings);
    });
}

function removeEventHandlers(oldBindings) {
    return Promise.all(oldBindings.map(function(binding) {
        return removeHandlerPromise(Office.select("binding#"+binding), Office.EventType.BindingDataChanged).then(function() {
            return releaseByIdPromise(Office.context.document.bindings, binding);
        });
    });
}

或者你可以为任何 returns 和 AsyncResult:

的异步操作给自己一个通用的 Promise-ifier
function promisify(obj, method) {
    var args = Array.prototype.slice.call(arguments, 2);
    return new Promise(function(resolve, reject) {
        args.push(function(asyncResult) {
            if (asyncResult.status == Office.AsyncResultStatus.Failed) {
                reject(asyncResult.error);
            } else {
                resolve(asyncResult.value);
            }
        });
        obj[method].apply(obj, args);
    });
}

那么你可以这样做:

var oldBindings = ["myBind1", "myBind2"]; // can be updated by other functions

function updateEventHandlers(newBindings) {
    removeEventHandlers(oldBindings).then(function() {
        addEventHandlers(newBindings);
    });
}

function removeEventHandlers(oldBindings) {
    return Promise.all(oldBindings.map(function(binding) {
        return promisify(Office.select("binding#"+binding), "removeHandlerAsync", Office.EventType.BindingDataChanged).then(function() {
            return promisify(Office.context.document.bindings, "releaseByIdAsync", binding);
        });
    });
}