你能等待JavaScript回调吗?

22

我试图使用jQuery alerts对话框库(源自http://abeautifulsite.net/notebook/87),以取代默认的警报框(在我看来,它们看起来相当糟糕)。这似乎是一个很棒的库,但没有使用jConfirm库的示例。

我需要做这样的事情:

function confirm() {
        var result = false;
        var response = false;
        jConfirm('are you sure?', 'Confirmation Dialog',
          function(r) {
            result = r;
            response = true;
            return r;
        });
        if (response == true) {
            alert(result);
            return result;
        }
        else {
            //wait for response
            alert('hi');
        }
    }

我的.NET按钮调用如下:

我在插件网站上留言(今早刚留),并进行了JavaScript的谷歌搜索,但没有结果。

有没有任何想法可以正确地使用回调来获取结果,在其余JavaScript执行之前呢?

谢谢。


更新:请查看作者的网站。他留言说它并不是真正意图按照我想要的方式运行。这篇文章的答案也证明了这一点。谢谢大家!! - AlignedDev
这里有一个插件:https://dev59.com/8Gw15IYBdhLWcg3wtNuO#12357337 - user1662811
9个回答

19

你刚刚遇到了JavaScript的一个严重限制。一旦你的代码进入异步世界,就没有办法回到传统的过程式执行流程。

在你的例子中,解决方案是创建一个循环等待响应填充。问题是JavaScript没有提供任何指令,可以让你无限循环而不占用100%的处理能力。所以你最终会阻塞浏览器,有时甚至会导致用户无法回答实际问题。

唯一的解决方法是坚持异步模型并保持它。我的建议是,你应该为必须执行一些异步工作的任何函数添加回调,这样调用者就可以在函数结束时执行一些操作。

function confirm(fnCallback) 
{
    jConfirm('are you sure?', 'Confirmation Dialog', function(r) 
    {
        // Do something with r 

        fnCallback && fnCallback(r); // call the callback if provided
    });
}

// in the caller

alert('begin');

confirm(function(r)
{
    alert(r);

    alert('end');
})

这不是JavaScript的限制,而是HTML网页工作方式。与JavaScript无关。 - dkretz
13
语言的限制在于无法在保持同一上下文的情况下让浏览器让步。如果可以的话,我们可以编写 "doAjax(); while( !ajaxDone() ){ Sleep(); }"。但是在JavaScript中这是不可能的,因为没有 Sleep 或类似的函数... - Vincent Robert
2
非常棒的异步和过程模型概念化。很多人的代码经常混合两种模型,却不知道如何解决只需要使用其中一种模型的问题,就像在我读这个答案之前的情况一样 ;) - Kevin
当然,这是Javascript的限制(如所述;现在在2019年已经不同了)。是的,网页异步工作,为用户提供方便。但事件循环并不是实现异步编程的唯一方式。 - user1071847

16
jConfirm('are you sure?', 'Confirmation Dialog',
    function(r) {
        result = r;
        response = true;
        return r;
    }
);
if (response == true) {
这暴露了对使用异步代码时发生事件顺序的误解。仅仅因为你在代码中将它写成行内形式并不意味着它会严格按照自上而下的顺序执行。
1. jConfirm被调用,其中一个参数是一个函数,jConfirm会记住这个函数。 2. jConfirm在页面上显示其UI并立即返回。 3. “if(response == true)”这一行执行。实际上,这应该只是“if(response)”,布尔比较是多余的。但无论如何,response当然是false。您的函数放弃并退出,将控制权交回给浏览器。 4. 用户单击jConfirm的UI。 5. jConfirm现在才开始行动,并回调您提供给它并之前已经记住的函数。 6. 您的嵌套函数设置response为true,但这对于“if(response == true)”条件来说太晚了,因此无法对其进行任何操作。
您已经将“//等待响应”作为另一种选择,但是您无法编写任何JavaScript代码来实现这一点。在浏览器可以在jConfirm UI上触发单击事件以使处理进行之前,您的函数必须返回才能将控制权返还给浏览器。
有方法使异步代码在同步上下文中运行(反之亦然)-特别是线程和协程(以及它们的有限关系生成器)。但是JavaScript没有这些功能,因此您必须编写符合库使用的同步或异步模型的代码。

2
这是一个很好的答案。但是关于如何构建回调函数的代码呢? - Cris Stringfellow

8

由于回调函数是异步的(至少在等待用户做出响应时是这样),在回调函数内部处理所需内容可能更容易:

function confirm() {
    jConfirm('are you sure?', 'Confirmation Dialog', function(r) {
        if (r) doSomething();
    });
}

@klogan [评论]

我猜你是从这里得到的?

该页面已经给出了答案:(请查看用法部分)

这些方法不会返回与confirm()和prompt()相同的值。您必须使用回调函数访问结果值。(有关详细信息,请参阅演示。)


@klogan

我的意思是,实际上没有一种简单的方法可以实现你想要的。你正在尝试将过程式编程和事件驱动编程联系起来——这是JavaScript无法帮助你做到的。

最简单(虽然有风险)的解决方案是使用伪无限循环。但是,如果callback从未被调用,那么你现在就有了一个真正的无限循环。而且,根据JavaScript引擎的不同,你可能会等待时间过长导致浏览器崩溃。

重点:你最好避免这个试图将事件驱动强制转换为过程式。

function confirm() {
    var result = false;
    var response = false;

    jConfirm('are you sure?', 'Confirmation Dialog',
      function(r) {
        result = r;
        response = true;
    });

    while(!response) continue; // wait
    return result;
}


谢谢回应,但我需要该函数返回回调函数的结果。因此,如果用户点击“确定”->返回true,如果用户点击“取消”->返回false。那么这将像内置的confirm函数一样工作。 - AlignedDev
我从他们的网页上得到了答案。 - AlignedDev
他们的演示代码没有展示如何使用确认结果。 - AlignedDev
谢谢,我实际上尝试使用 while 循环测试该解决方案,但 IE 会弹出一个警示对话框,表示 JavaScript 已挂起,你说的对,我不想这样做。我会等待作者的回复并发布解决方案。也许他们自己还没有完成。 - AlignedDev
4
无限循环不起作用。JavaScript是单线程的;当while循环在运行时,甚至无法与jQuery的确认UI进行交互。 - bobince

5

从技术上讲,是的,但我不建议在网站上这样做。

看一下Narrative JavaScript,它基于Narcissus

Narrative JavaScript是JavaScript语言的一个小扩展,可以为异步事件回调启用阻塞功能。这使得异步代码变得清晰易懂。

Selenium使用了这项技术。


更新

查看JavaScript Strands

JavaScript Strands 在 JavaScript 语言中添加了协程和协作线程支持,以启用异步事件回调的阻塞能力。这使得使用异步操作的代码更加线性、可读和易于管理。Strands 建立在 Neil Mix 编写的 Narrative JavaScript 基础之上,并且包括许多 Narrative JavaScript 的内容,包括本文档。在 JavaScript 中,您的代码不能简单地等待事件触发--事件必须始终由单独的异步事件处理程序处理。有时这是可以的,但它经常强制应该是简单语句序列的东西变得复杂。它还破坏了封装功能的能力,因为调用函数必须知道提供回调处理程序。Strands 提供了暂停和恢复执行线程的能力。当事件完成时,执行可以暂停恢复。这使您可以编写难以阅读的异步事件处理程序,将其封装在实现简单、线性、易于阅读的代码中。

1
你的 JavaScript Strands 的链接已经无效了。太糟糕了,因为它听起来很有趣。 - Leopd

0

就像那些人说的,似乎没办法!但是……在我国家(巴西)我们常说的一句话是:

我们可以这样做:

<h:commandLink styleClass="linkValor xExcluir" value="x"  
                     onclick="if(confirmar('Confirmar excluir.','Deseja realmente excluir este registro?', this)) return true; else return false;"
                     action="#{mBeanPesquisarLinhas.excluir}"/>

以及 JavaScript:

function confirmar(titulo, msg, elemento) { 

    if ($(elemento).attr('sim')) {      
        $(elemento).removeAttr('sim');      
        return true;
    } else if ($(elemento).attr('nao')) {       
        $(elemento).removeAttr('nao');      
        return false;
    } else {
        $("#dialog-confirm").html('<p>' + msg + '</p>').dialog({
            resizable : false,
            height : 200,
            modal : true,
            title : titulo,
            buttons : {
                "Sim" : function() {
                    $(this).dialog("close");
                    $(elemento).attr('sim', 'sim');
                    $(elemento).click();
                },
                "Não" : function() {
                    $(this).dialog("close");
                    $(elemento).attr('nao', 'nao');
                    $(elemento).click();
                }
            }
        });
    }

    return false;
}

这是同样的问题,但使用了jquery-ui。

再见,希望这能帮助到某些人。


0

将回调函数视为发送消息,这样可以使您的代码结构更加清晰。


0

我认为我已经想出了一个可能的解决方案来解决这个问题。我正在阅读这篇文章: http://treasure4developer.wordpress.com/2008/06/23/calling-postback-event-from-javascript/

基本上,这个想法是你通过javascript强制进行postback,起初我发现postback会起作用但不会调用我的按钮事件,但在阅读文章后,我发现我可以检测是否是javascript postback并调用一个方法来处理。

敬礼 DotnetShadow


0
我认为你需要像这样获取响应。我不认为你需要回调函数。
function confirm() {
        var response = jConfirm('are you sure?', 'Confirmation Dialog');
        if (response) {
            alert(result);
        }
        else {
            //wait for response
            alert('hi');
        }
    }

1
不幸的是,插件没有返回一个变量。这不是我开始使用时期望的结果。 - AlignedDev

0

这个东西可用。需要jQuery。

function myconfirm(kyssa, elm, e){ // neG
    if(jQuery('#confirmquestion').data('result')){
        var V = jQuery('#confirmquestion').data('result');
        jQuery('#confirmquestion').remove(); 
        return V == 'Y' ? true : false;         
    }else if(!jQuery('#confirmquestion').length){
        jQuery('body').append('<div id="confirmquestion">'+
        '<h4>Kinnitus</h4>'+
        '<div id="kyssa">'+kyssa+'</div>'+
        '<center>'+
        '<button onclick="jQuery(\'#confirmquestion\').data(\'result\', \'Y\');">Jah</button>&nbsp;'+
        '<button onclick="jQuery(\'#confirmquestion\').data(\'result\', \'N\');">Ei</button></div>'+
        '</center>');
        jQuery('#confirmquestion button').click(function(){
            jQuery(elm).trigger(e.type);
        })
    }       
    return false;
}

onChange="if(myconfirm(\'Saada kiri: \'+jQuery(this).find(\'option:selected\').html()+\' ?\', this, event)) { ... }"

CSS

#confirmquestion{
    border:1px solid #999;
    background:white;
    padding-bottom: 30px;
    position:fixed;
    width:300px;
    font-size:12px;
    top:45%;
    left:50%;
    margin-left:-150px;
}

#confirmquestion h4 {
    background:blue;
    color:white;
    margin:0;
    padding: 2px 5px;
    border-bottom:#777; 
    text-align:center;
}

#confirmquestion #kyssa {
    padding: 30px 25px;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接