共用方式為


錯誤處理

評估 M 運算式的結果會產生下列其中一種結果:

  • 產生單一值。

  • 「引發錯誤」,表示評估運算式的過程無法產生值。 錯誤包含單一記錄值,其可用來提供評估為何不完整的其他資訊。

錯誤可以從運算式內引發,且可以從運算式內處理。

引發錯誤

引發錯誤的語法如下:

error-raising-expression:
      error 運算式

文字值可以用來當作錯誤值的縮寫。 例如:

error "Hello, world" // error with message "Hello, world"

完整錯誤值是記錄,且可以使用 Error.Record 函式來建構:

error Error.Record("FileNotFound", "File my.txt not found",
     "my.txt")

上述運算式等同於:

error [ 
    Reason = "FileNotFound", 
    Message = "File my.txt not found", 
    Detail = "my.txt" 
]

引發錯誤會導致目前的運算式評估停止,且運算式評估堆疊會回溯,直到發生下列其中一種情況為止:

  • 已達到記錄欄位、區段成員或 let 變數,統稱為入口。 入口標示為發生錯誤,錯誤值會與該入口一起儲存,然後傳播。 對該入口的任何後續存取都會導致引發相同的錯誤。 記錄、區段或 let 運算式的其他入口不一定會受到影響 (除非它們存取先前標示為有錯誤的入口)。

  • 達到最上層運算式。 在此情況下,評估最上層運算式的結果會是錯誤,而不是值。

  • 達到 try 運算式。 在此情況下,會捕獲錯誤並以值的形式傳回。

處理錯誤

error-handling-expression (非正式地稱為「try 運算式」) 用於處理錯誤:

error-handling-expression:
      try protected-expression error-handleropt
protected-expression:
      expression
error-handler:
      otherwise-clause
      catch-clause
otherwise-clause:

      otherwise default-expression
default-expression:
      expression
catch-clause:
      catch catch-function
catch-function:
      (parameter-nameopt) => function-body

不使用 error-handler 評估 error-handling-expression 時,會發生下列情況:

  • 如果評估 protected-expression 未造成錯誤,且產生值 x,則 error-handling-expression 所產生值是下列形式的記錄:
    [ HasErrors = false, Value = x ]
  • 如果評估 protected-expression 引發錯誤值 e,則 error-handling-expression 結果是下列形式的記錄:
    [ HasErrors = true, Error = e ]

使用 error-handler 評估 error-handling-expression 時,會發生下列情況:

  • protected-expression 必須在 error-handler 之前評估。

  • 如果評估 protected-expression 引發錯誤,且唯有在這種情況下,才必須評估 error-handler

  • 如果評估 protected-expression 引發錯誤,則 error-handling-expression 所產生值是評估 error-handler 的結果。

  • 評估 error-handler 期間所引發的錯誤會進行傳播。

  • 當正在評估的 error-handlercatch-clause 時,就會叫用 catch-function。 如果該函式接受參數,則會將錯誤值當作其值傳遞。

下列範例說明在沒有引發錯誤時的 error-handling-expression

let
    x = try "A"
in
    if x[HasError] then x[Error] else x[Value] 
// "A"

下列範例會顯示引發錯誤,然後加以處理:

let
    x = try error "A" 
in
    if x[HasError] then x[Error] else x[Value] 
// [ Reason = "Expression.Error", Message = "A", Detail = null ]

使用 catch-clause 搭配可接受參數的 catch-function,即可使用較少的語法來重新撰寫上述範例:

let
    x = try error "A" catch (e) => e
in
    x
// [ Reason = "Expression.Error", Message = "A", Detail = null ]

otherwise-clause 可用於將 try 運算式所處理的錯誤取代為替代值:

try error "A" otherwise 1 
// 1

包含零參數 catch-functioncatch-clause 實際上是 otherwise-clause 的較長替代語法:

try error "A" catch () => 1 
// 1

如果 error-handler 也引發錯誤,則整個 try 運算式也會引發錯誤:

try error "A" otherwise error "B" 
// error with message "B"
try error "A" catch () => error "B" 
// error with message "B"
try error "A" catch (e) => error "B" 
// error with message "B"

記錄和 let 初始設定式中的錯誤

下列範例顯示記錄初始設定式,其中包含引發錯誤的欄位 A,且由兩個其他欄位 BC 存取。 欄位 B 不會處理 A 所引發的錯誤,但 C 會處理。 最後一個欄位 D 不會存取 A,因此不會受到 A 中的錯誤所影響。

[ 
    A = error "A", 
    B = A + 1,
    C = let x =
            try A in
                if not x[HasError] then x[Value]
                else x[Error], 
    D = 1 + 1 
]

評估上述運算式的結果為:

[ 
    A = // error with message "A" 
    B = // error with message "A" 
    C = "A", 
    D = 2 
]

M 中的錯誤處理執行時應該接近錯誤原因,以處理延遲欄位初始化和延遲關閉評估的影響。 下列範例顯示使用 try 運算式來處理錯誤的失敗嘗試:

let
    f = (x) => [ a = error "bad", b = x ],
    g = try f(42) otherwise 123
in 
    g[a]  // error "bad"

在此範例中,定義 g 的目的是要處理在呼叫 f 時所引發錯誤。 不過,此錯誤是由只在必要時執行的欄位初始設定式所引發,因此會在從 f 傳回記錄,並透過 try 運算式傳遞之後。

未實作錯誤

在開發運算式時,作者可能會想要省略運算式某些部分的實作,但可能仍然想要能夠執行運算式。 處理這種情況的其中一種方式是針對未實作部分引發錯誤。 例如:

(x, y) =>
     if x > y then
         x - y
     else
         error Error.Record("Expression.Error", 
            "Not Implemented")

省略號符號 (...) 可用作 error 的捷徑。

not-implemented-expression:
      ...

例如,下列等同於先前的範例:

(x, y) => if x > y then x - y else ...