- 数据可视化
【免费下载链接】cytoscape.js
Graph theory (network) library for visualisation and analysis
本篇指南围绕 Cytoscape.js 布局对象提供的layout.pon()(别名layout.promiseOn())方法展开,讲解如何用 Promise 的方式监听布局生命周期事件(如layoutstop、layoutready),在布局完成时自动解除监听并拿到事件对象,从而写出更简洁、可组合的异步流程控制代码。读完本文,你将掌握pon()的调用方式、它与.on()/.one()的关系、布局事件触发时序,以及它在「布局完成后执行后续逻辑」这类真实场景中的实战用法。
为什么需要pon():把事件回调改写成 Promise
Cytoscape.js 的布局是异步任务:创建一个布局对象后,layout.run()启动布局,而布局何时完成取决于算法类型与图规模。传统做法是使用layout.on()监听layoutstop事件,在回调里写后续逻辑:
var layout = cy.layout({ name: 'random' }); layout.on('layoutstop', function(event){ // 布局完成后要做的事 console.log('layout finished'); }); layout.run();回调写法的问题在于:后续逻辑被"嵌套"进事件回调中,当需要把布局完成作为异步流程的一个环节(比如与其它 Promise 组合、用 async/await 等待)时,就很不顺手。
layout.pon()正是为解决这个问题而生的:它把"监听一次事件"包装成一个 Promise,事件触发时 Promise 被 fulfill(兑现),你可以用.then()链式编排,或配合async/await使用。
layout.pon()基本用法
promiseOn.md 给出了最核心的用法示例:
var layout = cy.layout({ name: 'random' }); layout.pon('layoutstop').then(function( event ){ console.log('layoutstop promise fulfilled'); }); layout.run();拆解这段代码:
cy.layout({ name: 'random' })创建随机布局对象(仅创建,尚未运行);layout.pon('layoutstop')返回一个 Promise,该 Promise 在layoutstop事件触发时被兑现;.then(function(event){ ... })中的回调收到事件对象event,此时可以安全地执行"布局已完成"后的逻辑;layout.run()启动布局。
值得注意的是pon()必须绑定在同一个 layout 实例上,且只需在run()之前调用一次——Promise 在事件触发时自动兑现,无需手动resolve。这与layout.on()的长期监听语义不同:on()注册的回调在每次事件触发时都会执行,而pon()对应的事件只要触发一次,Promise 就被永久兑现。
源码剖析:pon()本质是.on()的一次性 Promise 包装
pon()的实现非常精简,完整代码位于 src/define/events.mjs:
// this is just a wrapper alias of .on() p.pon = p.promiseOn = function( events, selector ){ let self = this; let args = Array.prototype.slice.call( arguments, 0 ); return new Promise( function( resolve, reject ){ let callback = function( e ){ self.off.apply( self, offArgs ); resolve( e ); }; let onArgs = args.concat( [ callback ] ); let offArgs = onArgs.concat( [] ); self.on.apply( self, onArgs ); } ); };从源码可以提炼出几个关键事实:
pon与promiseOn是同一个函数,二者互为别名,可以随意互换;- 它内部调用
self.on.apply(self, onArgs),把用户传入的事件名与selector原样转发给.on(),只是额外追加了一个callback; - 回调中首先执行
self.off.apply(self, offArgs)自动解绑,因此事件每次最多只会兑现 Promise 一次,这一点与layout.one()(只触发一次)语义一致; resolve(e)把事件对象作为 Promise 的兑现值,因此.then(function(event){ ... })中拿到的event与.on()回调里的参数完全相同,包含type、layout等字段;- 实现中
reject实际上从未被调用,也就是说pon()返回的 Promise 只会 fulfill、不会 reject,不需要写catch分支处理事件监听失败的情况。
底层 Promise 实现
pon()使用的Promise并非依赖宿主环境的全局对象,而是来自 src/promise.mjs。该模块内置了一个严格的 Promises/A+ 1.1.1 兼容实现(Thenable/1.0.7),支持then、fulfill、reject、all、resolve、reject等标准 API,并且在文件末尾做了这样的导出决策(见 src/promise.mjs):
export default typeof Promise !== 'undefined' ? Promise : api;也就是说:当宿主环境(浏览器 / Node.js)提供全局Promise时,优先使用原生 Promise;否则回退到内置实现。这保证了 Cytoscape.js 在较老环境下也能正常使用pon(),同时在新环境下与用户代码中的原生 Promise 完全互通——例如Promise.all([layout.pon('layoutstop'), otherPromise])可以直接组合。
与on/one的对照
| 方法 | 触发次数 | 自动解绑 | 返回值 | 典型用途 |
|---|---|---|---|---|
layout.on() | 每次事件都触发 | 否 | layout 自身(可链式调用) | 长期监听、统计、日志 |
layout.one() | 仅首次触发 | 是 | layout 自身 | 一次性回调,如options.ready/options.stop |
layout.pon() | 仅首次触发 | 是 | Promise(兑现值为事件对象) | 将布局完成接入 Promise / async 流程 |
布局生命周期事件:layoutstart、layoutready、layoutstop
要充分发挥pon()的价值,需要理解 Cytoscape.js 布局在运行过程中依次发出的事件。官方文档run.md说明了关键规则:
如果布局是异步的(continuous,即连续布局),调用
layout.run()只是启动布局,函数随即返回;同步(discrete,离散)布局则会在run()返回前完成。无论哪种情况,布局启动时都会发出layoutstart事件;布局结束或被停止(如调用layout.stop())时发出layoutstop事件。
结合 src/collection/layout.mjs 的实现,布局的典型事件序列为:
layoutstart——布局开始执行位置计算时发出;layoutready——布局计算完成、节点位置就绪时发出(对应options.ready回调);layoutstop——布局完全停止时发出(对应options.stop回调),标志着所有相关动画与位置更新均已结束。
其中layoutstop是"布局真正结束"的最可靠信号,也是run.md与stop.md中反复强调的监听时机——因此 promiseOn.md 的示例正是监听layoutstop。
另外,如果布局是通过layout.stop()被提前终止的(例如异步的 cose 连续布局),layoutstop同样会被发出。相关逻辑位于 src/extension.mjs:
this.emit( 'layoutstop' );因此pon('layoutstop')既能捕获"自然完成",也能捕获"被手动停止"的情况,两种结局都归一到同一个 Promise 兑现点上,非常便于统一处理。
实战场景
场景一:布局完成后再执行导出 / 截图 / 后续计算
布局是异步的,直接在其后同步执行依赖位置的代码会拿到未更新的坐标。用pon()优雅地解决:
var layout = cy.layout({ name: 'cose' }); layout.pon('layoutstop').then(function(event){ var bb = cy.elements().boundingBox(); console.log('布局结束,包围盒:', bb); // 例如:此时再调用 cy.png() 导出图片,或基于新位置做分析 }); layout.run();这里.then()中的代码保证在节点坐标稳定之后才执行,规避了时序竞争。
场景二:把"布局完成"接入 Promise.all 并行等待
当多个异步任务需要同时等待时,pon()返回的 Promise 可以直接参与组合:
var layout = cy.layout({ name: 'random' }); var layoutDone = layout.pon('layoutstop'); var dataLoaded = fetch('/api/data').then(function(r){ return r.json(); }); Promise.all([layoutDone, dataLoaded]).then(function(values){ console.log('布局与数据加载都完成了'); }); layout.run();场景三:配合 async/await 线性书写
在支持async/await的环境(或经构建工具转译)中,布局流程可以写得像同步代码一样直白:
async function runLayoutAndWait(cy){ var layout = cy.layout({ name: 'grid' }); var done = layout.pon('layoutstop'); layout.run(); await done; console.log('grid 布局已完成'); return cy.elements().boundingBox(); }场景四:等待动画布局的所有动画帧结束
当布局选项开启了animate: true时(见 src/collection/layout.mjs),layoutstop会延迟到所有节点位移动画通过Promise.all(animations.map(a => a.promise()))全部播放完毕后才发出。此时pon('layoutstop')等待的不只是"位置算好",而是"界面上的动画全部播完":
var layout = cy.layout({ name: 'breadthfirst', animate: true, animationDuration: 500 }); layout.pon('layoutstop').then(function(){ console.log('所有节点位移动画均已结束,界面已稳定'); }); layout.run();这对于"布局动画结束后再叠加其它动画或交互"的衔接场景非常关键。
使用注意事项
- 绑定顺序:
pon()应在layout.run()之前调用,确保监听在事件发出前已经注册;示例中先pon后run的顺序是正确的。 - 一次性语义:
pon()触发后自动off(),只兑现一次。若需要持续监听每个layoutstop(例如循环重跑布局),应使用layout.on()。 - 事件对象可用:
.then()回调的第一个参数是事件对象event,可通过event.layout访问布局实例、通过event.type判断事件类型。 selector参数可传:pon(events, selector)的第二个参数与.on()的 selector 参数语义一致,用于过滤触发事件的目标元素,只是布局事件的触发者通常是 layout 自身,实战中较少使用。- 不依赖回调嵌套:与
options.stop回调不同,pon()不会覆盖或替代cy.layout({ stop: fn })里的配置回调——两者可以并存,pon()只是额外提供一条 Promise 通道。
小结
layout.pon()是 Cytoscape.js 为布局事件提供的一层极简 Promise 封装:底层实现只有十余行代码(src/define/events.mjs),却把on/off/ Promise 三件事一次性处理好。它让"布局完成"这个高频异步信号从回调嵌套中解放出来,可以直接参与then链、Promise.all与async/await组合,配合run.md中说明的layoutstart/layoutready/layoutstop事件时序,即可编写出既简洁又时序可靠的布局驱动逻辑。掌握它,是进一步编写基于 Cytoscape.js 的可视化工作流(如布局后导出、联动分析、多任务并行)的基础一步。
- 数据可视化
【免费下载链接】cytoscape.js
Graph theory (network) library for visualisation and analysis
相关推荐
Wasp 邮件功能实战:emailSender 配置、五大 Provider 选型与 send API 源码级解析
Wasp 邮件功能实战:emailSender 配置、五大 Provider 选型与 send API 源码级解析 Wasp 通过 app spec 中的 em
数据可视化Cytoscape.js布局算法与可视化技术详解
Cytoscape.js布局算法与可视化技术详解 本文全面解析了Cytoscape.js的核心布局算法体系,包括层次布局、圆形布局、网格布局、力导向布局和特殊用
数据可视化Cytoscape.js 集合事件监听器移除:eles.removeListener() 方法详解与源码解析
Cytoscape.js 集合事件监听器移除:eles.removeListener 方法详解与源码解析 导读 在 Cytoscape.js 中, eles.r
数据可视化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考