You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

升级Dash至2.7.1后Flask嵌入的Dash应用控制台报错求助

Dash升级至2.7.1后控制台出现两类未捕获Promise错误的排查与解决

问题现象

将嵌入在Flask中的Dash应用升级到2.7.1版本后,浏览器控制台出现两类未捕获(in promise)错误:

  1. A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received
  2. There is already a source with this ID

错误栈信息

Uncaught (in promise) Error: There is already a source with this ID

at r.addSource (async-plotlyjs.js:2:1020888)
    at i.addSource (async-plotlyjs.js:2:1219363)
    at l.addSource (async-plotlyjs.js:2:2988732)
    at async-plotlyjs.js:2:2989736
    at h (async-plotlyjs.js:2:2989770)
    at l.update (async-plotlyjs.js:2:2990100)
    at b.updateData (async-plotlyjs.js:2:2338377)
    at async-plotlyjs.js:2:2336961

错误截图

错误截图

应用后端日志无任何错误输出,现需明确错误原因及排查/解决方向。

可能原因分析

  1. Plotly.js异步更新冲突:Dash 2.7.1升级了内置Plotly.js版本,async-plotlyjs.js中地图数据源更新逻辑存在重复添加同一ID资源的问题,常见于回调频繁触发或组件重复渲染场景。
  2. 路由路径不匹配:Flask DispatcherMiddleware挂载Dash的路径(/dashboard/)与前端导航路径(如/tab1)不一致,导致页面切换时组件未正确卸载,旧数据源未清理就被重复添加。
  3. Figure数据无唯一标识:回调返回的地图数据未指定唯一uid,Plotly.js更新时无法识别已有数据源,尝试重复创建同ID资源。
  4. 静态资源加载异常:async-plotlyjs.js加载不完整或缓存异常,导致异步消息通道未正常建立就被关闭,触发第一类错误。

排查与解决建议

排查步骤

  • 检查回调触发频率:在fin函数中添加日志,确认inp输入框值变化时是否存在重复触发回调的情况。
  • 验证路由匹配:查看浏览器地址栏路径,确认Dash的requests_pathname_prefix、Flask挂载路径与前端导航路径是否统一。
  • 检查组件卸载状态:切换页面时,在浏览器DevTools的Elements面板查看#map组件是否被正确移除。

解决建议

  1. 为地图数据添加唯一uid
    在回调返回的scattermapbox数据中添加uid字段,确保每次更新的数据源ID唯一:

    data.append({
        "type": "scattermapbox",
        "uid": f"map-source-{val}" if val else "map-source-default",
        # 其他字段保持不变
    })
    
  2. 修正路由路径匹配

    • 前端导航路径改为带Dash前缀的格式:
      dbc.NavLink("Tab1", href="/dashboard/tab1", active="partial"),
      dbc.NavLink("Tab2", href="/dashboard/tab2", active="partial"),
      
    • 同步更新display_content函数的路径判断:
      if pathname in ["/dashboard/","/dashboard"]:
          return tab1.layout
      elif pathname == "/dashboard/comps":
          return comps.layout
      
  3. 指定稳定版Plotly.js(临时 workaround)
    在Dash应用初始化时强制使用稳定版Plotly.js,规避内置版本的Bug:

    application = dash.Dash(__name__,
                            requests_pathname_prefix='/dashboard/',
                            suppress_callback_exceptions = True,
                            meta_tags=[
                                {"name": "viewport", "content": "width=device-width, initial-scale=1"}
                            ],
                            external_stylesheets=external_stylesheets,
                            config={"plotlyJS": "https://cdn.plot.ly/plotly-2.16.1.min.js"}
                            )
    
  4. 升级至更高版本Dash
    Dash 2.7.1存在已知的地图组件更新Bug,升级到2.8.0及以上版本可修复该问题:

    pip install --upgrade dash==2.8.0
    
  5. 清理页面切换时的组件状态
    在display_content函数返回新布局前,通过dcc.Store保存组件状态,或重置地图组件的初始属性,避免重复渲染导致的资源冲突。

相关应用代码

tab1.py

import dash
from dash import dcc, html
import pandas as pd

df = pd.DataFrame({
                   'x': [1, 2, 3],
                   'Lat': [37.774322, 37.777035, 37.773033],
                   'Long': [-122.489761, -122.485555, -122.491220]
                 }) 

layout = html.Div(
                   dcc.Graph(id="map"),
                   dcc.Input(id="inp")
                 )

@app.callback(
              Output('map','figure'),
              Input('inp','value')
             )
def fin(val):
    cd_cols = ['x']
    data = []
    
    data.append({
        "type": "scattermapbox",
        "uid": f"map-source-{val}" if val else "map-source-default",
        "lat": df["Lat"],
        "lon": df["Long"],
        "name": "Location",
        "showlegend": False,
        "hoverinfo": "text",
        "mode": "markers",
        "clickmode": "event+select",
        "customdata": df.loc[:,cd_cols].values,
        "marker": {
            "symbol": "circle",
            "size": 8,
            "opacity": 0.7,
            "color": "black"
        }
    })

    layout = {
        "autosize": True,
        "hovermode": "closest",
        "mapbox": {
            "accesstoken": MAPBOX_KEY,
            "bearing": 0,
            "center": {
                "lat": 37.775,
                "lon": -122.488
            },
            "pitch": 0,
            "zoom": 13,
            "style": "satellite-streets",
        },
    }

    return {'data': data, 'layout': layout}

application.py

import dash
import flask
from dash import dcc, html
import dash_bootstrap_components as dbc
import os

SANDSTONE = "xxxxx"

external_stylesheets = [
    SANDSTONE,
    {
        'href': 'custom.css',
        'rel': 'stylesheet'
    },
    {
        'href': 'https://use.fontawesome.com/releases/v5.10.2/css/all.css',
        'rel': 'stylesheet'
    }
]

application = dash.Dash(__name__,
                        requests_pathname_prefix='/dashboard/',
                        suppress_callback_exceptions = True,
                        meta_tags=[
                            {"name": "viewport", "content": "width=device-width, initial-scale=1"}
                        ],
                        external_stylesheets=external_stylesheets,
                        config={"plotlyJS": "https://cdn.plot.ly/plotly-2.16.1.min.js"}
               )

server = application.server
application.title = "Stroom - Platform Demo"

index.py

import pandas as pd
import dash
from dash.dependencies import Input, Output
from dash import dcc, html
import dash_bootstrap_components as dbc
from application import application
from tabs import comps, tab1

server = application.server

application.layout = html.Div([
    html.Div([
        html.Div(
            html.Img(src='https://ss.s3.us-west-1.amazonaws.com/logo-black.png',height="100%"),
            style={"float":"right",
                   "width":"170px",
                   "height":"100px",
                   "margin-top":"-84px"
                   }
        ),
        html.Div(
            [
                html.H4("Market Intelligence", style={"textAlign":"center"}),
                html.Hr(),
                dbc.Nav(
                    [
                        dbc.NavLink("Tab1", href="/dashboard/tab1", active="partial"),
                        dbc.NavLink("Tab2", href="/dashboard/tab2", active="partial"),
                    ],
                    vertical=True,
                    fill=True,
                    pills=True,
                ),
            ],
            style = {
                "position": "fixed",
                "top": 0,
                "left": 0,
                "bottom": 0,
                "width": "10rem",
                "padding": "1rem 1rem",
                "background-color": "#f8f9fa",
            },
        ),
        dcc.Location(id='url'),
        html.Div(id='page-content'),
        dcc.Store(id="comps-store", storage_type="local"),
        dcc.Store(id="modal-store", storage_type="local"),
    ])
])

@application.callback(Output("page-content", "children"),
              [Input('url', 'pathname')]
             )
def display_content(pathname):
    print(pathname)
    if pathname in ["/dashboard/","/dashboard"]:
        return tab1.layout
    elif pathname == "/dashboard/comps":
        return comps.layout
    else:
        return dash.no_update

init.py

from flask import Flask, redirect
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, login_required
import sys
import os
sys.path.append("..")
from werkzeug.middleware.dispatcher import DispatcherMiddleware
from werkzeug.serving import run_simple
from index import application as dashApp

import pymysql

database = 'login'

server_auth = Flask(__name__, instance_relative_config=False)

server_auth.config['SECRET_KEY'] = os.environ["pwd"]
server_auth.config['SQLALCHEMY_DATABASE_URI'] = "mysql+pymysql://{}:{}@{}/{}".format(os.environ["user"],os.environ["pwd"],os.environ["host"], database)
server_auth.config['TESTING'] = True

db = SQLAlchemy(server_auth)
db.init_app(server_auth)

login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(server_auth)

from .models import users, init_db
init_db()

@login_manager.user_loader
def load_user(user_id):
    return users.query.get(int(user_id))

from .auth import auth as auth_blueprint
server_auth.register_blueprint(auth_blueprint)

from .main import main as main_blueprint
server_auth.register_blueprint(main_blueprint)

@server_auth.route('/dashboard')
@login_required
def dashboard():
    return redirect('/dashboard/')

app = DispatcherMiddleware(server_auth,
                           {'/dashboard': dashApp.server})

if __name__ == '__main__':
    run_simple('0.0.0.0', 80, app, use_reloader=True, use_debugger=True)

内容的提问来源于stack exchange,提问作者kms

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 05:30:53