浏览代码

feat: `eval_js()` support get value of JS Promise

wangweimin 4 年之前
父节点
当前提交
7bd09d5b77
共有 4 个文件被更改,包括 51 次插入20 次删除
  1. 1 0
      docs/spec.rst
  2. 16 15
      pywebio/session/__init__.py
  3. 11 1
      test/13.misc.py
  4. 23 4
      webiojs/src/handlers/script.ts

+ 1 - 0
docs/spec.rst

@@ -301,6 +301,7 @@ The ``spec`` fields of ``run_script`` commands:
 
 
 * code: str, code
 * code: str, code
 * args: dict, Local variables passed to js code
 * args: dict, Local variables passed to js code
+* eval: bool, whether to submit the return value of javascript code
 
 
 download
 download
 ^^^^^^^^^^^^^^^
 ^^^^^^^^^^^^^^^

+ 16 - 15
pywebio/session/__init__.py

@@ -339,6 +339,8 @@ def eval_js(expression_, **args):
     """Execute JavaScript expression in the user's browser and get the value of the expression
     """Execute JavaScript expression in the user's browser and get the value of the expression
 
 
     :param str expression_: JavaScript expression. The value of the expression need to be JSON-serializable.
     :param str expression_: JavaScript expression. The value of the expression need to be JSON-serializable.
+       If the value of the expression is a `promise <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise>`_,
+       ``eval_js()`` will wait for the promise to resolve and return the value of it. When the promise is rejected, `None` is returned.
     :param args: Local variables passed to js code. Variables need to be JSON-serializable.
     :param args: Local variables passed to js code. Variables need to be JSON-serializable.
     :return: The value of the expression.
     :return: The value of the expression.
 
 
@@ -361,22 +363,21 @@ def eval_js(expression_, **args):
         })()''', b=100)
         })()''', b=100)
         put_text(function_res)  # ..demo-only
         put_text(function_res)  # ..demo-only
 
 
+        ## ----
+        promise_res = eval_js('''new Promise(resolve => {
+            setTimeout(() => {
+                resolve('Returned inside callback.');
+            }, 2000);
+        });''')
+        put_text(promise_res)  # ..demo-only
+
+    .. versionchanged:: 1.3
+
+       The JS expression support return promise.
     """
     """
-    script = r"""
-    (function(WebIO){
-        let ____result____ = null;  // to avoid naming conflict
-        try{
-            ____result____ = eval(%r);
-        }catch{};
-        
-        WebIO.sendMessage({
-            event: "js_yield",
-            task_id: WebIOCurrentTaskID,  // local var in run_script command
-            data: ____result____ || null
-        });
-    })(WebIO);""" % expression_
-
-    run_js(script, **args)
+
+    from ..io_ctrl import send_msg
+    send_msg('run_script', spec=dict(code=expression_, args=args, eval=True))
 
 
     res = yield next_client_event()
     res = yield next_client_event()
     assert res['event'] == 'js_yield', "Internal Error, please report this bug on " \
     assert res['event'] == 'js_yield', "Internal Error, please report this bug on " \

+ 11 - 1
test/13.misc.py

@@ -40,6 +40,17 @@ def target():
     assert local._dict == {'age': 22, 10: '10'}
     assert local._dict == {'age': 22, 10: '10'}
     print(local)
     print(local)
 
 
+    # test eval_js promise
+    import random
+    val = random.randint(1, 9999999)
+    promise_res = yield eval_js('''new Promise((resolve,reject) => {
+        setTimeout(() => {
+            resolve(val);
+        }, 1);
+    });''', val=val)
+    print(promise_res, val)
+    assert promise_res == val
+
     # test pywebio.utils
     # test pywebio.utils
     async def corofunc(**kwargs):
     async def corofunc(**kwargs):
         pass
         pass
@@ -200,7 +211,6 @@ def test(server_proc: subprocess.Popen, browser: Chrome):
     percySnapshot(browser, name='misc')
     percySnapshot(browser, name='misc')
 
 
 
 
-
 def start_test_server():
 def start_test_server():
     pywebio.enable_debug()
     pywebio.enable_debug()
 
 

+ 23 - 4
webiojs/src/handlers/script.ts

@@ -1,5 +1,6 @@
 import {Command, Session} from "../session";
 import {Command, Session} from "../session";
 import {CommandHandler} from "./base";
 import {CommandHandler} from "./base";
+import {state} from "../state";
 
 
 
 
 export class ScriptHandler implements CommandHandler {
 export class ScriptHandler implements CommandHandler {
@@ -14,12 +15,30 @@ export class ScriptHandler implements CommandHandler {
     handle_message(msg: Command) {
     handle_message(msg: Command) {
         let script = msg.spec.code as string;
         let script = msg.spec.code as string;
         let args = msg.spec.args as { [i: string]: any };
         let args = msg.spec.args as { [i: string]: any };
-        let arg_names:string[] = ['WebIOCurrentTaskID'], arg_vals:any[] = [msg.task_id];
-        for(let key in args){
+
+        let arg_names: string[] = [];
+        let arg_vals: any[] = [];
+
+        for (let key in args) {
             arg_names.push(key);
             arg_names.push(key);
             arg_vals.push(args[key]);
             arg_vals.push(args[key]);
         }
         }
-        const script_func = new Function(...arg_names, script);
-        script_func(...arg_vals);
+
+        let res = null;
+        script = `return eval(${JSON.stringify(script)})`;
+        try {
+            const script_func = new Function(...arg_names, script);
+            res = script_func(...arg_vals);
+        } catch (e) {
+            console.log('Exception occurred in user code of `run_script` command: \n%s', e)
+        }
+        if (msg.spec.eval) {
+            // credit: https://stackoverflow.com/questions/27746304/how-do-i-tell-if-an-object-is-a-promise
+            Promise.resolve(res).then(function (value) {
+                state.CurrentSession.send_message({event: "js_yield", task_id: msg.task_id, data: value || null});
+            }).catch((error) => {
+                state.CurrentSession.send_message({event: "js_yield", task_id: msg.task_id, data: null});
+            });
+        }
     }
     }
 }
 }