Skip to content
Mithril.js 2
Main Navigation 指南API

繁體中文

English
简体中文
Español
Français
Русский
Português – Brasil
Deutsch
日本語
한국어
Italiano
Polski
Türkçe
čeština
magyar

繁體中文

English
简体中文
Español
Français
Русский
Português – Brasil
Deutsch
日本語
한국어
Italiano
Polski
Türkçe
čeština
magyar

外觀

Sidebar Navigation

API

核心 API

m(selector, attributes, children)

render(element, vnodes)

mount(root, component)

route(root, defaultRoute, routes)

request(options)

parseQueryString(string)

buildQueryString(object)

buildPathname(object)

parsePathname(string)

trust(html)

fragment(attrs, children)

redraw()

censor(object, extra)

可選 API

stream()

指南

本頁導覽

request(options) ​

描述 ​

發送 XHR (又稱 AJAX) 請求,並回傳 promise。

javascript
m.request({
  method: 'PUT',
  url: '/api/v1/users/:id',
  params: { id: 1 },
  body: { name: 'test' },
}).then(function (result) {
  console.log(result);
});

簽名 ​

promise = m.request(options)

參數類型是否必要描述
optionsObject是要傳入請求的選項。
options.methodString否要使用的 HTTP 方法。此值應為以下任一:GET、POST、PUT、PATCH、DELETE、HEAD 或 OPTIONS。預設值為 GET。
options.urlString是要將請求傳送到的 路徑名稱,並可使用 options.params 中的值進行插值。
options.paramsObject否要插值到 URL 中和/或序列化到查詢字串中的資料。
options.bodyObject否要序列化至請求 body 中的資料(適用於其他類型的請求)。
options.asyncBoolean否請求是否應為非同步處理。預設值為 true。
options.userString否用於 HTTP 授權的使用者名稱。預設值為 undefined。
options.passwordString否用於 HTTP 授權的密碼。預設值為 undefined。提供此選項是為了與 XMLHttpRequest 相容,但應避免使用,因為它會以純文字形式在網路上傳輸密碼。
options.withCredentialsBoolean否是否將 cookies 發送到第三方網域。預設值為 false。
options.timeoutNumber否請求在自動 終止 前可花費的毫秒數。預設值為 undefined。
options.responseTypeString否預期的回應類型。如果定義了 extract,則預設值為 "",如果缺少則預設值為 "json"。如果 responseType: "json",它會在內部執行 JSON.parse(responseText)。
options.configxhr = Function(xhr)否暴露底層的 XMLHttpRequest 物件,用於底層配置和可選的替換(透過回傳新的 XHR)。
options.headersObject否在傳送請求之前附加到請求的標頭(在 options.config 之前應用)。
options.typeany = Function(any)否要套用於回應中每個物件的建構函式。預設值為 恆等函數。
options.serializestring = Function(any)否要套用於 body 的序列化方法。預設值為 JSON.stringify,或者如果 options.body 是 FormData 或 URLSearchParams 的實例,則預設值為 恆等函數(亦即 function(value) {return value})。
options.deserializeany = Function(any)否要套用於 xhr.response 或標準化的 xhr.responseText 的反序列化方法。預設值為 恆等函數。如果定義了 extract,則將跳過 deserialize。
options.extractany = Function(xhr, options)否一個 Hook,用於指定應如何讀取 XMLHttpRequest 回應。適用於處理回應資料、讀取標頭和 Cookies。預設情況下,這是一個回傳 options.deserialize(parsedResponse) 的函式,當伺服器回應狀態碼指示錯誤或回應在語法上無效時,會拋出例外。如果提供了自訂 extract 回呼,則 xhr 參數是用於請求的 XMLHttpRequest 實例,而 options 是傳遞給 m.request 呼叫的物件。此外,將跳過 deserialize,並且從 extract 回呼回傳的值將按原樣保留,直到 Promise 解決。
options.backgroundBoolean否如果為 false,則在請求完成後重新繪製已掛載的元件。如果為 true,則不重新繪製。預設值為 false。
returnsPromise一個 Promise,在透過 extract、deserialize 和 type 方法傳輸後,解析為回應資料。如果回應狀態碼指示錯誤,則 Promise 會被拒絕,但可以透過設定 extract 選項來避免這種情況。

promise = m.request(url, options)

參數類型是否必要描述
urlString是要將請求傳送到的 路徑名稱。如果存在 options.url,則會覆蓋此值。
optionsObject否要傳入的請求選項。
returnsPromise一個 Promise,在透過 extract、deserialize 和 type 方法傳輸後,解析為回應資料

第二種形式主要等效於 m.request(Object.assign({url: url}, options)),只是它在內部不依賴於 ES6 全域 Object.assign。

如何閱讀簽名

運作方式 ​

m.request 實用工具是 XMLHttpRequest 的一個輕量級封裝,允許向遠端伺服器發送 HTTP 請求,以便從資料庫儲存和/或檢索資料。

javascript
m.request({
  method: 'GET',
  url: '/api/v1/users',
}).then(function (users) {
  console.log(users);
});

呼叫 m.request 會回傳一個 promise,並在其 promise 鏈完成後觸發重新繪製。

預設情況下,m.request 假設回應為 JSON 格式,並將其剖析為 JavaScript 物件(或陣列)。

如果 HTTP 回應狀態碼指示錯誤,則返回的 Promise 將被拒絕。提供 extract 回呼將防止 promise 拒絕。

典型用法 ​

以下是一個說明性範例,展示了一個使用 m.request 從伺服器檢索一些資料的元件。

javascript
var Data = {
  todos: {
    list: [],
    fetch: function () {
      m.request({
        method: 'GET',
        url: '/api/v1/todos',
      }).then(function (items) {
        Data.todos.list = items;
      });
    },
  },
};

var Todos = {
  oninit: Data.todos.fetch,
  view: function (vnode) {
    return Data.todos.list.map(function (item) {
      return m('div', item.title);
    });
  },
};

m.route(document.body, '/', {
  '/': Todos,
});

假設向伺服器 URL /api/items 發出請求會回傳 JSON 格式的物件陣列。

當在底部呼叫 m.route 時,會初始化 Todos 元件。呼叫 oninit,它會呼叫 m.request。這會從伺服器非同步檢索物件陣列。「非同步」表示 JavaScript 在等待伺服器的回應時會繼續執行其他程式碼。在這種情況下,這表示 fetch 返回,並且使用原始空陣列作為 Data.todos.list 來呈現元件。一旦對伺服器的請求完成,物件陣列 items 就會被指派給 Data.todos.list,並且元件會再次呈現,產生一個包含每個 todo 標題的 <div> 清單。

錯誤處理 ​

當非 file: 請求回傳的狀態不是 2xx 或 304 時,它會拒絕並顯示錯誤。此錯誤是一個普通的 Error 實例,並具有一些特殊屬性。

  • error.message 設定為原始回應文字。
  • error.code 設定為狀態碼本身。
  • error.response 設定為已剖析的回應,使用 options.extract 和 options.deserialize,就像處理正常回應一樣。

這在許多情況下都很有用,因為錯誤本身是可以處理的。如果您想偵測會話是否已過期 - 您可以執行 if (error.code === 401) return promptForAuth().then(retry)。如果您遇到 API 的節流機制,並且它返回了一個帶有 "timeout": 1000 的錯誤,您可以執行 setTimeout(retry, error.response.timeout)。

載入圖示和錯誤訊息 ​

以下是上述範例的擴充版本,它實作了載入指示器和錯誤訊息:

javascript
var Data = {
  todos: {
    list: null,
    error: '',
    fetch: function () {
      m.request({
        method: 'GET',
        url: '/api/v1/todos',
      })
        .then(function (items) {
          Data.todos.list = items;
        })
        .catch(function (e) {
          Data.todos.error = e.message;
        });
    },
  },
};

var Todos = {
  oninit: Data.todos.fetch,
  view: function (vnode) {
    return Data.todos.error
      ? [m('.error', Data.todos.error)]
      : Data.todos.list
      ? [
          Data.todos.list.map(function (item) {
            return m('div', item.title);
          }),
        ]
      : m('.loading-icon');
  },
};

m.route(document.body, '/', {
  '/': Todos,
});

此範例與之前的範例之間存在一些差異。在這裡,Data.todos.list 在一開始是 null。此外,還有一個額外的欄位 error 用於保存錯誤訊息,並且修改了 Todos 元件的視圖,以便在存在錯誤訊息時顯示錯誤訊息,如果 Data.todos.list 不是陣列,則顯示載入圖示。

動態 URL ​

請求 URL 可能包含插值:

javascript
m.request({
  method: 'GET',
  url: '/api/v1/users/:id',
  params: { id: 123 },
}).then(function (user) {
  console.log(user.id); // logs 123
});

在上面的程式碼中,:id 會被 params 物件中的資料填入,請求會變成 GET /api/v1/users/123。

如果 params 屬性中不存在相符的資料,則會忽略插值。

javascript
m.request({
  method: 'GET',
  url: '/api/v1/users/foo:bar',
  params: { id: 123 },
});

在上面的程式碼中,請求會變成 GET /api/v1/users/foo:bar?id=123

中止請求 ​

有時候,需要中止請求。例如,在自動完成/預先輸入元件中,您希望確保只有最後一個請求完成,因為通常自動完成器會在使用者輸入時觸發多個請求,並且由於網路的不可預測性,HTTP 請求可能會非順序完成。如果另一個請求在最後一個觸發的請求之後完成,則元件將顯示比最後一個觸發的請求最後完成時相關性較低(或可能錯誤)的資料。

m.request() 透過 options.config 參數暴露其底層的 XMLHttpRequest 物件,這允許您保存對該物件的參考,並在需要時呼叫其 abort 方法:

javascript
var searchXHR = null;
function search() {
  abortPreviousSearch();

  m.request({
    method: 'GET',
    url: '/api/v1/users',
    params: { search: query },
    config: function (xhr) {
      searchXHR = xhr;
    },
  });
}
function abortPreviousSearch() {
  if (searchXHR !== null) searchXHR.abort();
  searchXHR = null;
}

檔案上傳 ​

若要上傳檔案,首先您需要取得 File 物件的參考。最簡單的方法是從 <input type="file"> 取得。

javascript
m.render(document.body, [m('input[type=file]', { onchange: upload })]);

function upload(e) {
  var file = e.target.files[0];
}

上面的程式碼片段會呈現一個檔案輸入。如果使用者選擇一個檔案,則會觸發 onchange 事件,該事件會呼叫 upload 函數。e.target.files 是 File 物件的清單。

接下來,您需要建立一個 FormData 物件來建立一個 multipart request,這是一個特殊格式的 HTTP 請求,能夠在請求 body 中傳送檔案資料。

javascript
function upload(e) {
  var file = e.target.files[0];

  var body = new FormData();
  body.append('myfile', file);
}

接下來,您需要呼叫 m.request 並將 options.method 設定為使用 body 的 HTTP 方法(例如 POST、PUT、PATCH),並使用 FormData 物件作為 options.body。

javascript
function upload(e) {
  var file = e.target.files[0];

  var body = new FormData();
  body.append('myfile', file);

  m.request({
    method: 'POST',
    url: '/api/v1/upload',
    body: body,
  });
}

假設伺服器已設定為接受 multipart 請求,則檔案資訊將與 myfile 鍵相關聯。

多個檔案上傳 ​

可以在一個請求中上傳多個檔案。這樣做會使批次上傳具有原子性(atomic),即如果在上傳期間發生錯誤,則不會處理任何檔案,因此無法僅儲存部分檔案。如果您希望在網路發生故障時儲存盡可能多的檔案,則應考慮在單獨的請求中上傳每個檔案。

若要上傳多個檔案,只需將它們全部附加到 FormData 物件。使用檔案輸入時,您可以透過將 multiple 屬性新增到輸入來取得檔案清單:

javascript
m.render(document.body, [
  m('input[type=file][multiple]', { onchange: upload }),
]);

function upload(e) {
  var files = e.target.files;

  var body = new FormData();
  for (var i = 0; i < files.length; i++) {
    body.append('file' + i, files[i]);
  }

  m.request({
    method: 'POST',
    url: '/api/v1/upload',
    body: body,
  });
}

監控進度 ​

有時,如果請求本質上很慢(例如,大型檔案上傳),則需要向使用者顯示進度指示器,以表示應用程式仍在運作。

m.request() 透過 options.config 參數暴露其底層的 XMLHttpRequest 物件,這允許您將事件接聽程式附加到 XMLHttpRequest 物件:

javascript
var progress = 0;

m.mount(document.body, {
  view: function () {
    return [
      m('input[type=file]', { onchange: upload }),
      progress + '% completed',
    ];
  },
});

function upload(e) {
  var file = e.target.files[0];

  var body = new FormData();
  body.append('myfile', file);

  m.request({
    method: 'POST',
    url: '/api/v1/upload',
    body: body,
    config: function (xhr) {
      xhr.upload.addEventListener('progress', function (e) {
        progress = e.loaded / e.total;

        m.redraw(); // 告訴 Mithril.js 資料已變更,需要重新呈現
      });
    },
  });
}

上面的範例中,會呈現一個檔案輸入。如果使用者選擇一個檔案,則會啟動上傳,並且在 config 回呼中,會註冊一個 progress 事件處理常式。每當 XMLHttpRequest 中有進度更新時,就會觸發此事件處理常式。由於 XMLHttpRequest 的進度事件不是由 Mithril.js 的虛擬 DOM 引擎直接處理的,因此必須呼叫 m.redraw() 以向 Mithril.js 發出訊號,表示資料已變更,需要重新繪製。

將回應轉換為類型 ​

根據整體應用程式架構,可能需要將請求的回應資料轉換為特定的類別或類型(例如,統一剖析日期欄位或具有輔助方法)。

您可以將建構函式作為 options.type 參數傳遞,Mithril.js 將為 HTTP 回應中的每個物件實例化它。

javascript
function User(data) {
  this.name = data.firstName + ' ' + data.lastName;
}

m.request({
  method: 'GET',
  url: '/api/v1/users',
  type: User,
}).then(function (users) {
  console.log(users[0].name); // logs a name
});

在上面的範例中,假設 /api/v1/users 返回一個物件陣列,則將為陣列中的每個物件實例化 User 建構函式(即,呼叫為 new User(data))。如果回應返回單個物件,則該物件將用作 body 參數。

非 JSON 回應 ​

有時,伺服器端點不會回傳 JSON 回應:例如,您可能正在請求 HTML 檔案、SVG 檔案或 CSV 檔案。預設情況下,Mithril.js 會嘗試將回應剖析為 JSON。若要覆蓋該行為,請定義自訂 options.deserialize 函數:

javascript
m.request({
  method: 'GET',
  url: '/files/icon.svg',
  deserialize: function (value) {
    return value;
  },
}).then(function (svg) {
  m.render(document.body, m.trust(svg));
});

在上面的範例中,請求會檢索 SVG 檔案,不執行任何剖析(因為 deserialize 僅按原樣返回該值),然後將 SVG 字串呈現為受信任的 HTML。

當然,deserialize 函數可能會更複雜:

javascript
m.request({
  method: 'GET',
  url: '/files/data.csv',
  deserialize: parseCSV,
}).then(function (data) {
  console.log(data);
});

function parseCSV(data) {
  // 為了保持範例簡單,採用簡單的實作方式
  return data.split('\n').map(function (row) {
    return row.split(',');
  });
}

忽略上述 parseCSV 函數沒有處理正確的 CSV 剖析器會處理的許多情況,上面的程式碼會記錄一個陣列的陣列。

自訂標頭也可能在這方面有所幫助。例如,如果您正在請求 SVG,您可能需要相應地設定內容類型。若要覆寫預設的 JSON 請求類型,請將 options.headers 設定為與請求標頭名稱和值對應的鍵值對物件。

javascript
m.request({
  method: 'GET',
  url: '/files/image.svg',
  headers: {
    'Content-Type': 'image/svg+xml; charset=utf-8',
    Accept: 'image/svg, text/*',
  },
  deserialize: function (value) {
    return value;
  },
});

檢索回應詳細資訊 ​

預設情況下,Mithril.js 會嘗試將 xhr.responseText 剖析為 JSON,並回傳已剖析的物件。更詳細地檢查伺服器回應並手動處理它可能很有用。這可以透過傳遞自訂 options.extract 函數來完成:

javascript
m.request({
  method: 'GET',
  url: '/api/v1/users',
  extract: function (xhr) {
    return { status: xhr.status, body: xhr.responseText };
  },
}).then(function (response) {
  console.log(response.status, response.body);
});

options.extract 的參數是 XMLHttpRequest 物件,一旦其操作完成,但在將其傳遞到返回的 promise 鏈之前,因此如果處理擲回例外,promise 仍可能最終處於拒絕狀態。

向 IP 位址發出提取 ​

由於 URL 中偵測參數的方式(非常簡單),IPv6 位址區段會被誤認為是路徑參數插值,並且由於路徑參數需要一些東西來分隔它們才能正確插值,因此會拋出例外。

javascript
// 這不起作用
m.request('http://[2001:db8::990a:cd27:4d9e:79]:8080/some/path', {
  // ...
});

若要解決此問題,您應該將 IPv6 位址 + 連接埠對作為參數傳遞。

javascript
m.request('http://:host/some/path', {
  params: { host: '[2001:db8::990a:cd27:4d9e:79]:8080' },
  // ...
});

這不是 IPv4 位址的問題,您可以正常使用它們。

javascript
// 這將按照您的預期運作
m.request('http://192.0.2.15:8080/some/path', {
  // ...
});

為什麼選擇 JSON 而不是 HTML ​

許多伺服器端框架提供一個視圖引擎,該引擎在提供 HTML 之前(在頁面載入時或透過 AJAX)將資料庫資料插入到範本中,然後使用 jQuery 來處理使用者互動。

相比之下,Mithril.js 是一個專為胖客戶端應用程式設計的框架,這些應用程式通常會分別下載範本和資料,並透過 JavaScript 在瀏覽器中將它們組合在一起。在瀏覽器中進行範本的繁重工作可以帶來一些好處,例如透過釋放伺服器資源來降低營運成本。將範本與資料分離還可以更有效地快取範本程式碼,並在不同類型的客戶端(例如,桌面、行動裝置)之間實現更好的程式碼可重用性。另一個好處是 Mithril.js 啟用了 保留模式 UI 開發範例,這大大簡化了複雜使用者互動的開發和維護。

預設情況下,m.request 期望回應資料為 JSON 格式。在典型的 Mithril.js 應用程式中,該 JSON 資料通常由視圖使用。

您應該避免嘗試使用 Mithril 呈現伺服器產生的動態 HTML。如果您有一個現有的應用程式使用伺服器端範本系統,並且您希望重新架構它,請首先確定從一開始就努力是否可行。從胖伺服器架構遷移到胖客戶端架構通常是一項相當大的工作,並且涉及將邏輯從範本重構為邏輯資料服務(以及隨之而來的測試)。

資料服務可以透過許多不同的方式組織,具體取決於應用程式的性質。RESTful 架構在 API 提供者中很受歡迎,並且在存在大量高度事務性工作流程的情況下,通常需要 面向服務的架構。

為什麼選擇 XHR 而不是 fetch ​

fetch() 是一個較新的 Web API,用於從伺服器提取資源,類似於 XMLHttpRequest。

Mithril.js 的 m.request 使用 XMLHttpRequest 而不是 fetch() 的原因有很多:

  • fetch 尚未完全標準化,並且可能會受到規格變更的影響。
  • 可以在解析之前中止 XMLHttpRequest 呼叫(例如,為了避免即時搜尋 UI 中的競爭條件)。
  • XMLHttpRequest 為長時間執行的請求(例如,檔案上傳)提供進度接聽程式的 hook。
  • 所有瀏覽器都支援 XMLHttpRequest,而 Internet Explorer 和較舊的 Android(5.0 Lollipop 之前)不支援 fetch()。

目前,由於缺乏瀏覽器支援,fetch() 通常需要一個 polyfill,它未壓縮時超過 11kb - 幾乎是 Mithril.js 的 XHR 模組的三倍大。

儘管 Mithril.js 的 XHR 模組小得多,但它支援許多重要且不易實作的功能,例如 URL 插值 和查詢字串序列化,此外它還能夠無縫整合到 Mithril.js 的自動重繪子系統。fetch polyfill 不支援任何這些功能,並且需要額外的程式庫和模板程式碼才能達到相同的功能層級。

此外,Mithril.js 的 XHR 模組針對基於 JSON 的端點進行了最佳化,並使最常見的情況簡潔明瞭 - 即 m.request(url) - 而 fetch 需要一個額外的明確步驟才能將回應資料剖析為 JSON:fetch(url).then(function(response) {return response.json()})

在一些不常見的情況下,fetch() API 確實比 XMLHttpRequest 具有一些技術優勢:

  • 它提供了一個串流處理 API(在「視訊串流」意義上,而不是在反應式程式設計意義上),這可以提高非常大的回應的延遲和記憶體消耗(以程式碼複雜性為代價)。
  • 它整合到 Service Worker API,這提供了對網路請求發生方式和時間的額外控制層。此 API 還允許存取推播通知和背景同步功能。

在典型情況下,串流處理不會提供明顯的效能優勢,因為通常不建議從一開始就下載數 MB 的資料。此外,如果重複使用小緩衝區導致過多的瀏覽器重繪,則重複使用小緩衝區所獲得的記憶體收益可能會被抵消或無效。由於這些原因,僅建議對資源密集型應用程式選擇 fetch() 串流處理而不是 m.request。

避免反模式 ​

Promise 不是回應資料 ​

m.request 方法回傳一個 Promise,而不是回應資料本身。它無法直接回傳該資料,因為 HTTP 請求可能需要很長時間才能完成(由於網路延遲),並且如果 JavaScript 等待它,它將凍結應用程式,直到資料可用。

javascript
// 避免
var users = m.request('/api/v1/users');
console.log('list of users:', users);
// `users` 不是使用者清單,它是一個 promise

// 偏好
m.request('/api/v1/users').then(function (users) {
  console.log('list of users:', users);
});
Pager
上一頁route(root, defaultRoute, routes)
下一頁parseQueryString(string)

以 MIT 授權條款 發布。

版權所有 (c) 2024 Mithril Contributors

https://mithril.js.org/request.html

以 MIT 授權條款 發布。

版權所有 (c) 2024 Mithril Contributors