2020/05/06

JavaScript ES6 call DataSnap API with Promise Fetch

在【Async callback in JS DataSnap Framework】裡有提到可以使用【Handling the Result】,也就是Callback function,如此就能避免掉在XMLHttpRequest中已被棄用的【同步請求(Synchronous request)】。

不過呢,有一好就沒有二好,寫著寫著,我的程式碼就變得和下圖一樣:
傳說中的回呼地獄(Callback hell)
圖:取自網路

聽說JavaScript新標準ES6裡的Promise,它的出現就是為了解決Callback hell而發展出的解決方案。

XHRHttpRequest正常流程

JavaScript DataSnap framework 會自動產出 ServerFunction.js 檔,以【ReverseString】為例,程式碼部分片段如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
  /*
   * @param Value [in] - Type on server: string
   * @return result - Type on server: string
   */
  this.ReverseString = function(Value) {
    var returnObject = this.executor.executeMethod('ReverseString', "GET", [Value], arguments[1], true, arguments[2], arguments[3]);
    if (arguments[1] == null) {
      if (returnObject != null && returnObject.result != null && isArray(returnObject.result)) {
        var resultArray = returnObject.result;
        var resultObject = new Object();
        resultObject.Value = Value;
        resultObject.result = resultArray[0];
        return resultObject;
      }
      return returnObject;
    }
  };

按照範例來看,最終的resultObject會有【Value】和【result】兩個屬性,分別儲存【傳出值】和【回傳值】,官方範例裡的ServerFunctionInvoker.html,會將這兩個屬性一起展示以方便我們理解。

程式碼和運行結果會是:

1
2
3
4
5
6
7
8
function onReverseStringClick()
{
  var valueField = document.getElementById('valueField');
  var s = serverMethods().ReverseString(valueField.value);
  console.log(`Send Value is : ${s.Value}; Result data is : ${s.result}`);
  valueField.value = s.result;
}
// Send Value is : A B C; Result data is : C B A

可以看出回傳的物件有Value和result兩個屬性
可以看出回傳的物件有Value和result兩個屬性

理想終究是理想,太神化了


就在Server斷線再開時,就遭遇到傳說中的【undefined】:
不僅回傳值沒有,就連輸入值也沒有

因為回傳的沒有【result】,自然也就不會走建立屬性流程
回傳結果是一個例外內容,在範例中沒提到,只有遇到才會知道。(拭淚)

XHRHttpRequest小結

官方操作API的範例在正常流程下沒有問題,而且相當好理解,只是同步應用的方式已被列為棄用,應當避免再使用同步AJAX。
由於【403 SessionExpired】是開發DataSnap服務時非常容易遇到的錯誤,所以使用【Promise fetch】時,除了正常流程外,例外流程也務必要設計進去。


Promise fetch 開發前

官方有寫出完整且詳盡的XHRHttpRequest設計內容,這可能是為了相容於IE系列所寫出的。行動平台的瀏覽器大多有支援最新的JavaScript標準,故直上fetch是沒有問題的。

在操作fetch之前,先來看看XHRHttpRequest發出API請求時的詳細內容:
關鍵在於【Authorization】和【Pragma】這兩個Header參數:

Authorization:驗證方式【Basic】和BASE64化的帳號密碼
Pragma:傳送SessionID,可以透過它在DataSnap Server裡進行狀態管理,Server關閉時,Session Manager(TDSSessionManager)也會被清空,這點要留意!

得知要注意的地方後,就可以使用fetch來如法泡製。

fetch基本用法

 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
31
32
33
34
35
36
37
"use strict"

function fetchAsyncReverseString() {
  let valueField = document.getElementById("valueFieldAsync");
  fetch(`http://localhost:8080/datasnap/rest/TServerMethods1/ReverseString/${valueField.value}`,
    {
      method:'GET',
      headers:{
        'Accept':'application/json',
        'Content-Type':'text/plain;charset=UTF-8',
        'Authorization':'Basic Og==',
        'pragma':`dssession=${getSessionID()}`
      }
    }
  )
  .then(
    function (response) {
      if (!response.ok)
        throw Error(response.statusText)
      return response.json()
    }
  )
  .then(
    function (returnObject) {
      let dataResult=''

      if (returnObject !== null && returnObject.result !== null && isArray(returnObject.result)) {
        dataResult = returnObject.result[0]
      }

      valueField.value = dataResult
    }
  )
  .catch(
    error => console.error(error)
  )
}

Promise只需要使用【then】和【catch】就能完成所有工作,也就不會有Callback hell的悲劇發生。

瀏覽器記錄的fetch Request內容:
可以看到操作fetch和xhr分別發送request


fetch header內容也可以和xhr相同


錯誤訊息也可以被正確取出
因為fetch就是要從零開始刻,所以程式碼很長是必然的。
有了官方XHRHttpRequest範例的經典,fetch也就可以順利的設計出來。

同步傳輸影響到UX

Promise的核心概念是非同步傳輸,在Facebook中的動態訊息也是用到大量非同步傳輸,如果要等動態訊息全部載入再顯示,那使用者可能會等到抓狂。
但如果是編輯資料時先顥示輸入框一陣子,等資料下載完成後再填入到輸入框給使用者修改,那使用者會為了系統給出空白資料而感到困惑。


所以有必要另外寫一支同步傳輸的函式。

同步傳輸?那不是已被棄用了嗎?

XHRHttpRequest同步傳輸雖然被棄用,但上帝關了一扇窗,必定會開一道門















並沒有。











在fetch中,可以用另一種方式達到類似同步傳輸效果:await。

**await故名思義是【等待】之義,它會等待(async)Promise函式完成後返回【解決 ( resolve )】 或【出錯 ( reject )】 後才會進行下一步,故不完全是【同步】之義。

改寫同步傳輸函式如下:
 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
"use strict"

async function fetchSyncReverseString() {
  let valueField = document.getElementById("valueFieldSync");
  const response = await fetch(`http://localhost:8080/datasnap/rest/TServerMethods1/ReverseString/${valueField.value}`,
    {
      method:'GET',
      headers:{
        'Accept':'application/json',
        'Content-Type':'text/plain;charset=UTF-8',
        'Authorization':'Basic Og==',
        'pragma':`dssession=${getSessionID()}`
      }
    }
  )
  if (!response.ok)
    throw Error(response.statusText)

  let returnObject = await response.json()

  let dataResult=''

  if (returnObject !== null && returnObject.result !== null && isArray(returnObject.result)) {
    dataResult = returnObject.result[0]
  }
  valueField.value = dataResult;
}


使用新標準的Promise fetch就能滿足同步/非同步兩種要求。

以上資訊提供給各位參考,謝謝各位收看,我們下次見。



***下回預告:DataSnap 狀態管理初探。***

See also

沒有留言:

張貼留言