Skip to content

建议嵌入块中的js支持异步函数,并且错误信息打印到控制台 #13775

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wish5115 opened this issue Jan 10, 2025 · 8 comments
Assignees

Comments

@wish5115
Copy link

wish5115 commented Jan 10, 2025

In what scenarios do you need this feature?

如图

image

先说异步函数

参见源代码

image

修改前在new function函数体中只能这样使用

//!js

return (async ()=>{
    const result = await query();
    return result;
})();

// 或

const asyncFunc = async () => {
    const result = await query();
    return result;
}

return asyncFunc();

修改后可以这样使用

return  await query();

显然后者更简洁和优雅。

再说错误信息

参见源代码

image

思源拦截了错误信息,既无抛出错误也无打印错误信息,只是返回了空数组,界面显示就是没匹配任何内容。

这个非常不友好,如果函数体内有语法错误,应该输出到控制台,这样开发起来更方便。

要想捕获错误,必须自己在代码中try{}catch(e){},如下,非常麻烦,尤其对小白用户,非常不友好。

//!js

try{
    return query("select `aa` as `aa__w80_c:blue_0`, `bb` from ?", [fromTable('20250110154352-pdr5v3t'),item]);
} catch(e) {
    console.error(e)
    return [];
}

Describe the optimal solution

嵌入块中的js支持异步函数,并且错误信息打印到控制台

Describe the candidate solution

No response

Other information

No response

@wish5115
Copy link
Author

wish5115 commented Jan 10, 2025

补充说明一点

嵌入块中有时可能需要使用第三方的js或代码片段,但嵌入块中无法保证其他的代码片段都已加载后才执行,这样的话,就可能报错第三方函数不存在。

建议new Function中传入一个等待函数,比如whenExist函数,这样的话

需要第三方函数时,可以这样 await whenExist(()=>alasql!==undefined);

否则,需要在每个函数体中都写一遍类似whenExist这个函数,挺麻烦的,最主要的是对小白用户不友好,如果改进后,结合第三方插件或js代码,这个嵌入查询的体验能大大改善,使用体验能逼近obsidian的dataview了,通过alasql能实现对数据库和表格的sql查询甚至连表,对于做统计和汇总来说非常方便,体验也好。

现在的问题是,这个查询功能,主要业务还没开始,得塞进去一大堆业务无关的代码,小白用户光看这些业务无关的代码就看晕了,何况使用。

whenExist函数参考如下:

// 等待元素或变量存在
function whenExist(selector, parentNode = null, timeout = 5000, errorMsg = '') {
    return new Promise((resolve, reject) => {
        let timeoutId;
        let isResolved = false;
        const check = () => {
            let element = null;
            if (typeof selector === 'function') {
                element = selector();
            } else {
                element = (parentNode||document).querySelector(selector);
            }
            if (element) {
                isResolved = true;
                if(timeoutId) clearTimeout(timeoutId);
                resolve(element);
            } else {
                requestAnimationFrame(check);
            }
        };
        check();
        // 设置超时兜底
        if(timeout > 0) timeoutId = setTimeout(() => {
            if(isResolved) return;
            console.error(errorMsg||`Element not found within ${timeout}ms`);
            reject(new Error(errorMsg||`Element not found within ${timeout}ms`));
        }, timeout);
    });
}

@wish5115
Copy link
Author

wish5115 commented Jan 10, 2025

再提一点,如果能传入是加载,还是用户点击执行的参数,就更好了。这一点在做是否自动加载时很有用。

我现在是通过嵌入块是否第一次被加载或第一次被动态加载来判断的,虽然已经实现了,如果有原生支持就最好了。

如果不好加就算了。

@88250
Copy link
Member

88250 commented Jan 10, 2025

@frostime 邀请参与讨论该提案

@wish5115
Copy link
Author

建议官方认真考虑下。

最主要的是对小白用户不友好,如果改进后,结合第三方插件或js代码,这个嵌入查询的体验能大大改善,使用体验能逼近obsidian的dataview了,通过alasql能实现对数据库和表格的sql查询甚至连表,对于做统计和汇总来说非常方便,体验也好。

现在的问题是,这个查询功能,主要业务还没开始,得塞进去一大堆业务无关的代码,小白用户光看这些业务无关的代码就看晕了,何况使用。

@frostime
Copy link
Contributor

frostime commented Jan 11, 2025

Issue 标题提出的两点我都同意,而且实现起来应该也不复杂。

  1. 关于异步函数这个我同意,源代码当中其实只要自动把传入的 code 包裹一层就行了,实现起来应该很方便

  2. 关于报错,JS 代码我觉得主要需要注意的报错是两个地方

    1. 代码构建的报错,主要是传入 new Function 的代码编写不正确什么

      try {
        let buggyFunction = new Function("console.log('hello'); var a = "); // 语法错误,缺少赋值
        // 不会执行到这里
      } catch (e) {
        console.error("创建函数时出现错误:", e); // 输出:创建函数时出现错误: SyntaxError: Unexpected token ')'
      }
    2. 运行时错误

      buggyFunction = new Function("console.log('hello'); throw new Error('This is a runtime error!');");
      
      try {
        buggyFunction(); // 调用函数,触发运行时错误
      } catch (e) {
        console.error("执行函数时出现错误:", e); // 输出:执行函数时出现错误: Error: This is a runtime error!
      }

    做法可以是:在 new Function 和执行函数外面加一层 try catch,然后如果出现错误,就退出嵌入块渲染,把错误信息打印在嵌入块的 item element 里面的 textContent就行

后面他提到的内容,我没有太抓住要点。

首先,我对 whenExist 代码的必要性感到困惑。在思源笔记中,嵌入块代码的重新执行很常见:无论是重新打开文档、加载 protyle,还是手动点击嵌入块的刷新按钮,都会触发嵌入块的重渲染。
如果 JavaScript 查询依赖于一个外部异步加载的依赖项,而第一次自动执行嵌入块查询时由于缺少该依赖项而无法正常查询,那么在我看来,解决方案很简单——稍等片刻,按下 F5 刷新文档,protyle 会重新执行查询。

可能是思考角度不一样,总之这一点上我 GET 不到。

对于「传入加载类型参数」的建议,我认为需要谨慎对待。虽然实现细节有待商榷,但必须坚持一个原则:render/blockRender.ts 作为底层模块,不应该反向依赖高级生命周期状态,这样会破坏模块的职责边界。相关状态只能通过参数注入,而非在模块内部获取。

不过,你提到的「能传入是加载,还是用户点击执行的参数」似乎没有考虑周全。

在我的印象中会造成嵌入块刷新的场景不是两种,而是三种:

  1. 用户手动刷新
  2. 打开文档,触发的 protyle 加载事件
  3. 多端同步更新(特别是开启同步感知后)情况下,自动更新远端修改过的、且在本端正在打开的文档;这个我之前简单研究过一下,似乎走独立的刷新逻辑,无法用 protyle 事件捕获到,不过我未找到具体源码位置

@wish5115
Copy link
Author

wish5115 commented Jan 11, 2025

@frostime 感谢巨佬的回复!

不愧是巨佬,研究的就是深入。

@frostime @88250

这里我解释下whenExist的必要性。

这个场景主要是在第一次加载时,比如刷新页面时,刚好有个文档是当前页面,且该页面中有嵌入块,且该嵌入块依赖第三方代码。

比如,当我在嵌入块中写一个代码,但这个代码需要依赖第三方代码,但第三方代码并不一定在嵌入块的代码执行时已经加载完毕。这时候就会报错第三方库的函数不存在。虽然通过一定的延迟或报错后手动刷新重新执行就解决了。

但报错也产生了,对于不明白原理的用户看到的可能就是报错信息和未按预期执行,并不知道需要刷新下就能解决。

如果要解决这个问题,就需要用户手动添加延迟代码或写等待第三库加载完毕的代码,而这个whenExist就是这个代码,就是为了等待第三方库加载完毕的。如果嵌入块函数体中没有自带这个函数,那么这个函数就要用户自己实现,且不说是否好实现,每个嵌入块中都写入一次也麻烦不是。因此,建议官方加入这样一个函数,减少用户的工作,对用户的使用体验也更加友好。

另外,这个whenExist并不会影响嵌入块第二次及之后的刷新情况,因为这个函数中,每次刷新都会首先检查一次目标函数是否已存在,已存在就跳过了,直接执行后面的代码,只有当目标函数不存在时才会停下来,等待目标函数出现后才继续执行后面的代码。

关于职责边界问题,我同意巨佬的观点,底层不应该依赖上层,但不知这里在调用blockRender的地方是否方便参数传入?也正如F佬说的那样,自动更新的块刷新获取不到。关于自动同步,我也找过代码,没找到。 @88250 能否给点指导?

@frostime 感谢提供的嵌入块刷新场景!自动更新的刷新之前确实没考虑到这一点。

@88250
Copy link
Member

88250 commented Jan 12, 2025

后台更新块的场景比如搜索替换、修改定义块关联更新引用处的锚文本等,目前没有统一机制能够通知前端,这个我觉得暂时还是靠用户手动刷新吧。

@wish5115
Copy link
Author

后台更新块的场景比如搜索替换、修改定义块关联更新引用处的锚文本等,目前没有统一机制能够通知前端,这个我觉得暂时还是靠用户手动刷新吧。

好的,感谢D大的回复,我是本来是想在嵌入块代码里判断是用户手动刷新还是系统自动刷新的,然后针对不同情况做不同处理,如果不好处理就算了,这个问题不大,使用场景并不多。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants