我怎样才能让多个承诺操纵相同的 DOM 元素作为副作用并获得一致的结果?

How can I have multiple promises manipulate the same DOM element as a side effect with consistent results?

我有以下 Dojo 1.9 代码异步加载 2 个表(一些名称已更改):

function loadPanel1() {                                              

    // set 'Loading Tool' status message                                                     
    var actionStatus = dom.byId("globalError");                                              
    var oldStatus = actionStatus.innerHTML;                                                  
    var oldStatusClass = actionStatus.className;                                             
    actionStatus.className = "globalInfo";                                                   
    actionStatus.innerHTML = globalInfoMessage                                               
            + loadingMessage;                                                  

    // Post the data to the server                                                           
    request                                                                                  
            .post("loadPanel1.action", {                                     
                data : {                                                                     
                    "projectOid" : projectOid,                                               
                    "oid" : projectStreamOid                                                 
                }                                                                            
            })                                                                               
            .then(                                                                           
                    function(response) {                                                     
                        if (CommonUtils.checkForLoginPage(response)) {                       
                            return;                                                          
                        }                                                                    

                        dom.byId("Panel1").innerHTML = response;     

                        // restore previous action status                                    
                        var actionStatus = dom.byId("globalError");                          
                        actionStatus.innerHTML = oldStatus;                                  
                        actionStatus.className = oldStatusClass;                             
                        connectLinks();                                                      
                    },                                                                       
                    function(error) {                                                        
                        if (error instanceof SyntaxError) {                                  
                            location.reload(true);                                           
                        } else if (error instanceof Error) {                                 
                            var actionStatus = dom                                           
                                    .byId("globalError");                                    
                            actionStatus.innerHTML = globalErrorMessage                      
                                    + loadingErrorMessage;                     
                            actionStatus.className = "globalError";                          
                        }                                                                    
                    });                                                                      
}

//connectLinks connects event handlers to several links in Panel1 that reload the panel using loadPanel1() when successful.

/**                                                                                          
 * Posts the search form criteria to the server via ajax call.                               
 */                                                                                          
function loadPanel2() {                                               

    // set 'Loading Tool' status message                                                     
    var actionStatus = dom.byId("globalError");                                              
    var oldStatus = actionStatus.innerHTML;                                                  
    var oldStatusClass = actionStatus.className;                                             
    actionStatus.className = "globalInfo";                                                   
    actionStatus.innerHTML = globalInfoMessage                                               
            + loadingMessage;                                                  

    // Post the data to the server                                                           
    request                                                                                  
            .post("loadPanel2.action", {                                      
                data : {                                                                     
                    "projectOid" : projectOid,                                               
                    "oid" : projectStreamOid                                                 
                }                                                                            
            })                                                                               
            .then(                                                                           
                    function(response) {                                                     
                        if (CommonUtils.checkForLoginPage(response)) {                       
                            return;                                                          
                        }                                                                    

                        dom.byId("Panel2").innerHTML = response;      

                        // restore previous action status                                    
                        var actionStatus = dom.byId("globalError");                          
                        actionStatus.innerHTML = oldStatus;                                  
                        actionStatus.className = oldStatusClass;                             
                    },                                                                       
                    function(error) {                                                        
                        if (error instanceof SyntaxError) {                                  
                            location.reload(true);                                           
                        } else if (error instanceof Error) {                                 
                            var actionStatus = dom                                           
                                    .byId("globalError");                                    
                            actionStatus.innerHTML = globalErrorMessage                      
                                    + loadingErrorMessage;                     
                            actionStatus.className = "globalError";                          
                        }                                                                    
                    });                                                                    
}

我遇到的问题本质上是竞争条件:

  1. loadPanel1 启动并在本地缓存旧状态;
  2. loadPanel1 在状态中显示加载消息;
  3. loadPanel1 开始加载面板;
  4. loadPanel2启动并缓存loadPanel1放置的加载消息;
  5. loadPanel2用相同的消息替换加载消息;
  6. loadPanel2 开始加载面板 2;
  7. loadPanel1 完成加载面板并将它缓存的旧状态放回去;
  8. loadPanel2 完成加载并用它缓存的加载消息替换旧状态 loadPanel1!

一个复杂的问题是,有时,当页面加载时,globalError 已经在显示一个状态,并且该状态不应该被任何一个 loadPanel 方法删除。因此,无论 loadPanel1() 还是 loadPanel2() 最后完成,都需要保留该状态。此外,Panel1 本身有几个链接可以改变 Panel1 的内容,依次添加状态消息并重新加载面板(之后消息应该仍然存在)。 Panel2 加载后不会重新加载。

所以有几个工作流程:

  1. 页面加载时没有状态消息,两个面板都已加载,无论哪个面板何时完成,都不应显示任何状态消息;
  2. 页面加载状态消息,两个面板都加载,无论哪个面板何时完成,原始状态消息都应保留;
  3. 页面和面板已加载。用户执行更改 panel1 内部状态的操作。该操作显示状态消息,面板 1 已重新加载,操作显示的状态消息应在面板加载后保留。
  4. 加载面板时出现问题,在这种情况下,错误消息应显示为状态消息,无论何时、哪个以及有多少面板加载失败。

我忍不住认为有一个使用外部方法缓存操作消息的优雅解决方案,但 Google 在这方面并没有真正帮助我。大多数关于使用加载消息进行多个异步加载的文章都涉及在与最终内容加载到的相同位置显示加载消息,这在这种情况下没有帮助。

解决方案是不要有要替换的单个状态消息。相反,使它成为一个列表,每个组件都可以向其中添加、更改或删除自己的消息。你的逻辑应该是这样的

load(x) {
    const message = MessagePanel.newMessage();
    message.setContent(loader);
    message.show();
    fetch(x).then(res => {
        if (res.isMessage)
            message.setContent(res);
        else
            message.hide();
    }, err => {
        message.setContent(err);
    });
}

超时后,或在用户交互时,您需要再次隐藏消息,以免列表无限增长。

如果您确实 want/can 只显示一条消息,请 MessagePanel 管理状态。 load 函数应该做完全相同的事情,但面板可能会决定只显示列表中的顶部消息,或者最近显示的消息。

在与同事讨论最佳方法后,我决定让异步加载的组件使用单独的消息显示,而不是尝试在同一显示中处理消息。