2018/06/28

Async callback in JS DataSnap Framework

This is a question about a obsessive-compulsive disorder patient. photo from



In the default Datasnap REST application, if there is a specified build of the Sample Methods :

The "echostring" and "ReverseString" two method and corresponding pages are added to the template. By opening the Reversestring template, you can learn that JavaScript is written like this:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function onReverseStringClick()
{
  if (loginRequired && (AdminInst == null))
  {
    showLogin(true);
    return;
  }
  var valueField = document.getElementById('valueField');
  var s = serverMethods().ReverseString(valueField.value);
  valueField.value = s.result;
}

ReverseString program code taken from the TDSProxyGenerator component automatically generated "serverfunctions.js" file, and then go into this.executeMethodURL function :
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  this.executeMethodURL = function(url, contentParam, requestType, callback, hasResult, accept) {
    //.
    //.
    //async is only true if there is a callback that can be notified on completion
    var useCallback = (callback != null);
    request.open(requestType, url, useCallback);

    if (useCallback)
    {
      request.onreadystatechange = function() {
        if (request.readyState == 4)
        {
          //the callback will be notified the execution finished even if there is no expected result
          JSONResult = hasResult ? parseHTTPResponse(request) : null;
          callback(JSONResult, request.status, owner);
        }
      };
    }
    //.
    //.
    request.send(contentParam);

    //if a callback wasn't used then simply return the result.
    //otherwise, return nothing because this function will finish executing before
    //the server call returns, so the result text will be empty until it is passed to the callback
    if (hasResult && !useCallback)
    {
      return parseHTTPResponse(request);
    }
  };


Datasnap JS Program code in the ReverseString paradigm given parameters do not contain callback, so "Request.open (RequestType, URL, useCallback)" the useCallback always be FALSE, which means that async is not used, that is, "sync" processing is enabled.


This is the "Direct Style" in JavaScript, which is now executes correctly in all browsers.


However, the direct style is easy to understand and learn, but because of JavaScript's single-threaded characteristics, if the direct style Will directly affect the user experience, in a long (yes, long) future will be "direct style" deprecated.
When FireFox and Chrome executes an example, a warning is already present:
[Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.
But, like Delphi, most delphier will turn a blind eye to the "Warning" chosen by the IDE. If you are the kind of person who will see Warning but turn a blind eye, that gives you 3 seconds ...




































Ok, we continue to watch.  :D

From the This.executeMethodURL back one layer, in the serverfunctions.js file ReverseString is below:

1
2
  this.ReverseString = function(Value) {
    var returnObject = this.executor.executeMethod('ReverseString', "GET", [Value], arguments[1], true, arguments[2], arguments[3]);


The callback corresponds to the position of the arguments[1], arguments is a JavaScript default object, corresponding to the parameters in the function, and can be understood with function (arguments[).

Ex. function(Value) :
arguments[0] = Value;

The JavaScript parameter is implicitly given, and the actual call the code like this :


serverMethods().ReverseString('A B C', 'C B A') // is right.

arguments contents :
arguments[0] = 'A B C';
arguments[1] = 'C B A';
arguments[2] = null;   // Valid reads, [3], [4], [5] ..

In ReverseString, we know that arguments[1] as long as the callback parameters can be changed to ReverseString the "Asynchronous (Async)" mode, then we have to build a function:

1
2
3
4
function fetchReverStr(data){
  var valueField = document.getElementById('valueField');
  valueField.value = data.result;
}

And ReverseString JS code below :
1
2
3
4
5
function onReverseStringClick()
{
  var valueField = document.getElementById('valueField');
  serverMethods().ReverseString(valueField.value, fetchReverStr);
}


Ok,  0 Warning finish!



============中文版本===========

這是關於某位強迫症患者的問題 photo from



在預設的 DataSnap REST Application 中,如果有指定建立 Sample Methods:

會在模板中加入『EchoString』和『ReverseString』兩個 Method 和對應的網頁。

打開 ReverseString 模板,可以得知 JavaScript 是這麼寫的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function onReverseStringClick()
{
  if (loginRequired && (AdminInst == null))
  {
    showLogin(true);
    return;
  }
  var valueField = document.getElementById('valueField');
  var s = serverMethods().ReverseString(valueField.value);
  valueField.value = s.result;
}


ReverseString 程式碼取自於 TDSProxyGenerator 元件自動產生 serverfunctions.js 檔案,再深入會追朔到 ServerFunctionExecutor.js 裡的 this.executeMethodURL:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  this.executeMethodURL = function(url, contentParam, requestType, callback, hasResult, accept) {
    //.
    //.
    //async is only true if there is a callback that can be notified on completion
    var useCallback = (callback != null);
    request.open(requestType, url, useCallback);

    if (useCallback)
    {
      request.onreadystatechange = function() {
        if (request.readyState == 4)
        {
          //the callback will be notified the execution finished even if there is no expected result
          JSONResult = hasResult ? parseHTTPResponse(request) : null;
          callback(JSONResult, request.status, owner);
        }
      };
    }
    //.
    //.
    request.send(contentParam);

    //if a callback wasn't used then simply return the result.
    //otherwise, return nothing because this function will finish executing before
    //the server call returns, so the result text will be empty until it is passed to the callback
    if (hasResult && !useCallback)
    {
      return parseHTTPResponse(request);
    }
  };


DataSnap JS 程式碼在 ReverseString 範例中所給予的參數並不包含 callback,因此 request.open(requestType, url, useCallback) 的 useCallback 一定為 false,表示不採用 async,也就是啟用『同步』處理。

這就是 JavaScript 裡的『直接風格(Direct style)』,目前各家瀏覽器都可以正確執行。

然而直接風格雖然容易理解和學習,但礙於 JavaScript 的單執行緒特性,若採用直接風格
會直接影響使用者的使用體驗,在很久 (對,很久) 的將來後會把『直接風格』棄用。

FireFox 執行範例時就已經出現警語:
因為會影響使用者的使用體驗,已棄用主執行緒中的同步 XMLHttpRequest。若需更多資訊請參考 http://xhr.spec.whatwg.org/

Chrome 則是:
[Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.

不過,就像 Delphi 一樣,大多數的 Delphier 都會把 IDE 顯示的『Warning 』選擇視而不見。如果您也是那種會看到 Warning 卻視而不見的,那給您 3 秒鐘……



































之後,我們繼續看下去。(笑)

從 this.executeMethodURL 往回推一層,在 serverfunctions.js 檔案的 ReverseString 是這麼寫的:

1
2
  this.ReverseString = function(Value) {
    var returnObject = this.executor.executeMethod('ReverseString', "GET", [Value], arguments[1], true, arguments[2], arguments[3]);


callback 對應的就是 arguments[1] 的位置,arguments 是 JavaScript 內定物件,對應到 function 裡的參數,也可以用 function (arguments[]) 理解。

以 function(Value) 為例:
arguments[0] = Value;

而 JavaScript 的參數是隱式給予,實際呼叫的時候就算是這樣寫:
serverMethods().ReverseString('A B C', 'C B A'),也是合法的使用。

這時的arguments 內容是:
arguments[0] = 'A B C';
arguments[1] = 'C B A';
arguments[2] = null;   // 合法讀取,[3], [4], [5]...以此類推。

在 ReverseString 中,我們知道 arguments[1] 只要給予 callback 參數,就能讓 ReverseString 改走『非同步(async)』模式,那我們只要自建一個 function:

1
2
3
4
function fetchReverStr(data){
  var valueField = document.getElementById('valueField');
  valueField.value = data.result;
}

接著把 ReverseString JS 程式碼改成:

1
2
3
4
5
function onReverseStringClick()
{
  var valueField = document.getElementById('valueField');
  serverMethods().ReverseString(valueField.value, fetchReverStr);
}


最後,又解決了一位不具名的 0 Warning 強迫症患者。


See also:

沒有留言:

張貼留言