Lingara Lingara 文档 学习指南 API 库 应用 构建 网页版
语言: 中文简体

错误与重试

本页译自英文。如两者有出入,以英文页面为准。 阅读英文页面

库抛出的每一个失败都属于四种错误之一,而这四种错误共享同一个基类型,所以一个处理程序就能把它们全部捕获。

  • API 错误。 API 拒绝了请求。它带有 HTTP 状态码、一个可供分支判断的稳定 code,以及一条使用你所请求语言的消息。
  • OAuth 错误。 令牌端点拒绝了你的凭据或你请求的权限范围。
  • 维护错误。 API 因维护而短暂不可用。
  • 传输错误。 没有收到可用的应答:连接失败或超时,或者响应无法读取。

取消调用不算错误。它会以你所用语言自身的取消机制呈现。

哪些会被重试

429 或 503 如果在 Retry-After 头中告知了需要等待多久,且等待时间不超过 60 秒,就会在恰好等待这么久之后重试。一次调用最多尝试 3 次。把最大次数设为 1 即可关闭重试。

其余情况都会立即抛出:

  • 没有 Retry-After 的 429 或 503;
  • 超过 60 秒的等待,库不会在你的调用中耗费这段时间。该错误带有 retry_after,你可以自己安排重试;
  • 传输错误,因为请求可能已经到达 API;
  • 任何已经送出过事件的流。
代码示例语言

TypeScript

import { ApiError, MaintenanceError, OAuthError, TransportError } from "@lingara/api";

try {
  await client.getUsage();
} catch (e) {
  if (e instanceof ApiError) {
    // A refusal from the API: e.status, e.code (stable) and e.message (localised).
    console.error(e.status, e.code, e.message, e.retryAfter ?? "");
  } else if (e instanceof OAuthError) {
    // The token endpoint refused the credentials or the scopes.
    console.error(e.status, e.error, e.description ?? "");
  } else if (e instanceof MaintenanceError) {
    console.error("under maintenance; retry after", e.retryAfter ?? "a while");
  } else if (e instanceof TransportError) {
    // No usable answer: connect, tls, reset, timeout, and so on.
    console.error("transport:", e.kind);
  } else {
    throw e;
  }
}

Rust

use lingara::Error;

let result = client.get_usage().await;
if let Some(wait) = result.as_ref().err().and_then(Error::retry_after) {
    eprintln!("the API asks you to retry after {wait:?}");
}
match result {
    Ok(usage) => println!("{} allowance rows", usage.allowance.len()),
    // A refusal from the API: status, code (stable) and message (localised).
    Err(Error::Api(e)) => eprintln!("{} {} {}", e.status, e.code, e.message),
    // The token endpoint refused the credentials or the scopes.
    Err(Error::OAuth(e)) => eprintln!("{} {} {}", e.status, e.error, e.description.unwrap_or_default()),
    Err(Error::Maintenance(_)) => eprintln!("the API is under maintenance"),
    // No usable answer: connect, tls, reset, timeout, and so on.
    Err(Error::Transport(e)) => eprintln!("transport: {}", e.kind),
    Err(other) => return Err(other.into()),
}

Go

_, err := client.GetUsage(ctx)
var apiErr *lingara.APIError
var oauthErr *lingara.OAuthError
var maintenance *lingara.MaintenanceError
var transport *lingara.TransportError
switch {
case err == nil:
case errors.As(err, &apiErr):
	// A refusal from the API: Status, Code (stable) and Message (localised).
	fmt.Println(apiErr.Status, apiErr.Code, apiErr.Message)
	if apiErr.RetryAfter != nil {
		fmt.Println("retry after", *apiErr.RetryAfter)
	}
case errors.As(err, &oauthErr):
	// The token endpoint refused the credentials or the scopes.
	fmt.Println(oauthErr.Status, oauthErr.ErrorCode, oauthErr.Description)
case errors.As(err, &maintenance):
	fmt.Println("under maintenance")
	if maintenance.RetryAfter != nil {
		fmt.Println("retry after", *maintenance.RetryAfter)
	}
case errors.As(err, &transport):
	// No usable answer: connect, tls, reset, timeout, and so on.
	fmt.Println("transport:", transport.Kind)
default:
	// ctx was cancelled or timed out: errors.Is(err, context.Canceled).
	fmt.Println(err)
}

Java

try {
  client.getUsage();
} catch (LingaraException e) {
  if (e instanceof ApiException api) {
    // A refusal from the API: status(), code() (stable) and getMessage() (localised).
    System.out.println(api.status() + " " + api.code() + " " + api.getMessage());
    api.retryAfter().ifPresent(wait -> System.out.println("retry after " + wait));
  } else if (e instanceof OAuthException oauth) {
    // The token endpoint refused the credentials or the scopes.
    System.out.println(oauth.status() + " " + oauth.error() + " " + oauth.description());
  } else if (e instanceof MaintenanceException maintenance) {
    System.out.println("under maintenance");
    maintenance.retryAfter().ifPresent(wait -> System.out.println("retry after " + wait));
  } else if (e instanceof TransportException transport) {
    // No usable answer: CONNECT, TLS, RESET, TIMEOUT, and so on.
    System.out.println("transport: " + transport.kind());
  }
}
// An interrupted call throws java.util.concurrent.CancellationException instead.

Kotlin

try {
    client.getUsage()
} catch (e: LingaraException) {
    when (e) {
        // A refusal from the API: status, code (stable) and message (localised).
        is ApiException -> println("${e.status} ${e.code} ${e.message}; retry after ${e.retryAfter}")
        // The token endpoint refused the credentials or the scopes.
        is OAuthException -> println("${e.status} ${e.error} ${e.description}")
        is MaintenanceException -> println("under maintenance; retry after ${e.retryAfter}")
        // No usable answer: CONNECT, TLS, RESET, TIMEOUT, and so on.
        is TransportException -> println("transport: ${e.kind}")
    }
}
// A cancelled call throws the caller's own CancellationException instead.

Ruby

begin
  client.get_usage
rescue Lingara::ApiError => e
  # A refusal from the API: status, code (stable) and message (localised).
  puts "#{e.status} #{e.code}: #{e.message}"
  puts "retry after #{e.retry_after}s" if e.retry_after
rescue Lingara::OAuthError => e
  # The token endpoint refused the credentials or the scopes.
  puts "#{e.status} #{e.error}: #{e.description}"
rescue Lingara::MaintenanceError => e
  puts "under maintenance"
  puts "retry after #{e.retry_after}s" if e.retry_after
rescue Lingara::TransportError => e
  # No usable answer: :connect, :tls, :reset, :timeout, and so on.
  puts "transport: #{e.kind}"
end

PHP

use Lingara\Exception\ApiException;
use Lingara\Exception\LingaraException;
use Lingara\Exception\MaintenanceException;
use Lingara\Exception\OAuthException;
use Lingara\Exception\TransportException;

try {
    $client->getUsage();
} catch (LingaraException $e) {
    if ($e instanceof ApiException) {
        // A refusal from the API: status, errorCode (stable) and message (localised).
        echo $e->status(), ' ', $e->errorCode(), ': ', $e->getMessage(), "\n";
        echo 'retry after: ', $e->retryAfter() ?? 'not given', "\n";
    } elseif ($e instanceof OAuthException) {
        // The token endpoint refused the credentials or the scopes.
        echo $e->status(), ' ', $e->error(), ': ', $e->description(), "\n";
    } elseif ($e instanceof MaintenanceException) {
        echo 'under maintenance; retry after: ', $e->retryAfter() ?? 'not given', "\n";
    } elseif ($e instanceof TransportException) {
        // No usable answer: connect, tls, reset, timeout, and so on.
        echo 'transport: ', $e->kind()->value, "\n";
    }
}