升级Dash至2.7.1后Flask嵌入的Dash应用控制台报错求助
Dash升级至2.7.1后控制台出现两类未捕获Promise错误的排查与解决
问题现象
将嵌入在Flask中的Dash应用升级到2.7.1版本后,浏览器控制台出现两类未捕获(in promise)错误:
A listener indicated an asynchronous response by returning true, but the message channel closed before a response was receivedThere 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
错误截图

应用后端日志无任何错误输出,现需明确错误原因及排查/解决方向。
可能原因分析
- Plotly.js异步更新冲突:Dash 2.7.1升级了内置Plotly.js版本,
async-plotlyjs.js中地图数据源更新逻辑存在重复添加同一ID资源的问题,常见于回调频繁触发或组件重复渲染场景。 - 路由路径不匹配:Flask DispatcherMiddleware挂载Dash的路径(
/dashboard/)与前端导航路径(如/tab1)不一致,导致页面切换时组件未正确卸载,旧数据源未清理就被重复添加。 - Figure数据无唯一标识:回调返回的地图数据未指定唯一
uid,Plotly.js更新时无法识别已有数据源,尝试重复创建同ID资源。 - 静态资源加载异常:
async-plotlyjs.js加载不完整或缓存异常,导致异步消息通道未正常建立就被关闭,触发第一类错误。
排查与解决建议
排查步骤
- 检查回调触发频率:在
fin函数中添加日志,确认inp输入框值变化时是否存在重复触发回调的情况。 - 验证路由匹配:查看浏览器地址栏路径,确认Dash的
requests_pathname_prefix、Flask挂载路径与前端导航路径是否统一。 - 检查组件卸载状态:切换页面时,在浏览器DevTools的Elements面板查看
#map组件是否被正确移除。
解决建议
为地图数据添加唯一uid
在回调返回的scattermapbox数据中添加uid字段,确保每次更新的数据源ID唯一:data.append({ "type": "scattermapbox", "uid": f"map-source-{val}" if val else "map-source-default", # 其他字段保持不变 })修正路由路径匹配
- 前端导航路径改为带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
- 前端导航路径改为带Dash前缀的格式:
指定稳定版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"} )升级至更高版本Dash
Dash 2.7.1存在已知的地图组件更新Bug,升级到2.8.0及以上版本可修复该问题:pip install --upgrade dash==2.8.0清理页面切换时的组件状态
在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
相关产品推荐
相关产品推荐

