如何等待第 3 方 JavaScript 函数到 return

How to wait for 3rd party JavaScript function to return

给定以下代码片段

var empowerInstance = null;

function onClick_btnSendMessage() {
    var childIFrame = window.document.getElementById("editorFrame");
    if (!empowerInstance) {
        empowerInstance = EditorAPI.getInstance(childIFrame.contentWindow, window.location.origin);
    }
    empowerInstance.document.hasChanged(hasChangedCallback);
}

function hasChangedCallback(returnValue) {
    console.log("empowerInstance.document.hasChanged = " + returnValue.isDirty);
    if (returnValue.success === true && returnValue.isDirty === true) {
        empowerInstance.document.save(saveCallback);
    }
}

function saveCallback(returnValue) {
    console.log("empowerInstance.document.save = " + returnValue.success);
    if (returnValue.success === false) {
        console.log(returnValue.message);
    }
}

window.addEventListener("DOMContentLoaded", function (event) {
    console.log("DOM fully loaded and parsed");
    if (typeof location.origin === "undefined")
        window.location.origin = window.location.protocol + "//" + window.location.host;
    document.getElementById("btnSendMessage").addEventListener("click", onClick_btnSendMessage);
});

我不想连接按钮,而是想通过激活 Bootstrap 选项卡事件来触发代码。

$('a[data-toggle="tab"]').on("shown.bs.tab", function (e) {

    onClick_btnSendMessage(); // Naive way, as this does not wait

    var target = $(e.target).attr("data-EditorUrl"); // activated tab
    var childIFrame = $("#editorFrame");
    childIFrame.attr("src", target);

});

所以我的问题是 "How do I wait on this function to complete before changing the source of childIFrame?"。

empowerInstance.document.hasChanged(hasChangedCallback);

我在概念上理解 Promises 和 Callbacks 的使用,但是编写一个正确运行的是另一回事。

已更新

此版本经过重构以消除按钮处理程序,从而提高了可读性。

用法也很重要。当页面第一次加载时,它位于选项卡上。此选项卡与托管在 iFrame 中的文档相关联。如果用户编辑此文档然后尝试更改选项卡,我想调用 dirty/save 的检查,然后一旦保存,移动到下一个 tab/document。还有一种情况,在tabs/documents之间切换不会导致保存,因为文档不脏。

var empowerInstance = null;

function hasChangedCallback(returnValue) {
    console.log("empowerInstance.document.hasChanged = " + returnValue.isDirty);
    if (returnValue.success === true && returnValue.isDirty === true) {
        empowerInstance.document.save(saveCallback);
    }
}

function saveCallback(returnValue) {
    console.log("empowerInstance.document.save = " + returnValue.success);
    if (returnValue.success === false) {
        console.log(returnValue.message);
    }
}

$(function () {

    if (typeof location.origin === "undefined") {
        window.location.origin = window.location.protocol + "//" + window.location.host;
    }

    $('a[data-toggle="tab"]').on("shown.bs.tab", function (e) {

        var childIFrame = $("#editorFrame");
        if (!empowerInstance) {
            empowerInstance = EditorAPI.getInstance(childIFrame[0].contentWindow, window.location.origin);
        }
        empowerInstance.document.hasChanged(hasChangedCallback);// Need to wait for completion 

        var target = $(e.target).attr("data-EditorUrl"); // activated tab
        childIFrame.attr("src", target);

    });
});

谢谢, 斯蒂芬

你可以使用一些 higher order functions 来做你想做的事。不是将 hasChangedCallbacksaveCallback 直接传递给 empowerInstance.document 方法,而是调用一个函数,该函数 returns 这些回调,但也会传递您自己的回调一旦所有异步操作最终完成,就会调用。这是它的样子:

$('a[data-toggle="tab"]').on("shown.bs.tab", function (e) {
    var target = $(e.target).attr("data-EditorUrl"); // activated tab

    onClick_btnSendMessage(function () {
        var childIFrame = $("#editorFrame");
        childIFrame.attr("src", target);
    });
});

function onClick_btnSendMessage(myCallback) {
    var childIFrame = window.document.getElementById("editorFrame");
    if (!empowerInstance) {
        empowerInstance = EditorAPI.getInstance(childIFrame.contentWindow, window.location.origin);
    }
    empowerInstance.document.hasChanged(getHasChangedCallback(myCallback));
}

function getHasChangedCallback(myCallback) {
    return function hasChangedCallback(returnValue,  myCallback) {
        console.log("empowerInstance.document.hasChanged = " + returnValue.isDirty);
        if (returnValue.success === true && returnValue.isDirty === true) {
            empowerInstance.document.save(getSaveCallback(myCallback));
        }
    } 
}

function getSaveCallback(myCallback) {
    return function saveCallback(returnValue) {
        console.log("empowerInstance.document.save = " + returnValue.success);
        if (returnValue.success === false) {
            console.log(returnValue.message);
        }

        myCallback && myCallback(); // make sure myCallback isn't null before invoking
    }
}

它不是很吸引人,但它应该能满足您的需求。

我重构了您的代码以展示如何使用 promises 完成此操作。

function onClick_btnSendMessage() {
  var childIFrame = window.document.getElementById("editorFrame");
  if (!empowerInstance) {
    empowerInstance = EditorAPI.getInstance(childIFrame.contentWindow, window.location.origin);
  }
  var doc = empowerInstance.document;
  return hasChanged(doc).then(function() { return save(doc) })
}


function hasChanged(doc) {
  return new Promise(function(resolve, reject) {
    doc.hasChanged(function(returnValue) {
      if (returnValue.success === true && returnValue.isDirty === true) {
        resolve(returnValue)
      } else {
        reject(returnValue)
      }
    })
  })
}

function save(doc) {
  return new Promise(function(resolve, reject) {
    doc.save(function(returnValue) {
      if (returnValue.success === false) {
        console.log(returnValue.message);
        reject(returnValue)
      } else {
        resolve(returnValue)
      }
    })
  })
}

// ------

$('a[data-toggle="tab"]').on("shown.bs.tab", function(e) {

  onClick_btnSendMessage().then(function() {
    var target = $(e.target).attr("data-EditorUrl"); // activated tab
    var childIFrame = $("#editorFrame");
    childIFrame.attr("src", target);

  }).catch(function(error) {
     // handle the error
     console.error('Error!', error)
  })


});