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 (aka AJAX) запросы и возвращает промис.

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 и/или сериализации в строку запроса (query string).
options.bodyObjectНетДанные для сериализации в тело запроса (для методов, отличных от GET и HEAD).
options.asyncBooleanНетОпределяет, должен ли запрос быть асинхронным. По умолчанию true.
options.userStringНетИмя пользователя для HTTP-авторизации. По умолчанию undefined.
options.passwordStringНетПароль для HTTP-авторизации. По умолчанию undefined. Эта опция предоставлена для совместимости с XMLHttpRequest, но её использование не рекомендуется, так как она отправляет пароль в виде открытого текста по сети.
options.withCredentialsBooleanНетОпределяет, следует ли отправлять куки в сторонние домены. По умолчанию 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)НетХук для указания способа чтения ответа XMLHttpRequest. Полезен для обработки данных ответа, чтения заголовков и куки. По умолчанию это функция, которая возвращает options.deserialize(parsedResponse), вызывая исключение, когда код состояния ответа сервера указывает на ошибку или когда ответ синтаксически недействителен. Если предоставлен пользовательский колбэк extract, параметр xhr является экземпляром XMLHttpRequest, используемым для запроса, а options - это объект, переданный в вызов m.request. Кроме того, deserialize будет пропущен, и значение, возвращаемое из callback extract, будет использовано как есть при разрешении промиса.
options.backgroundBooleanНетЕсли false, перерисовывает смонтированные компоненты по завершении запроса. Если true, не перерисовывает. По умолчанию false.
returnsPromisePromise, который разрешается в данные ответа после того, как они были обработаны методами extract, deserialize и type. Если код состояния ответа указывает на ошибку, promise отклоняется, но этого можно избежать, установив опцию extract.

promise = m.request(url, options)

АргументТипОбязательныйОписание
urlStringДаИмя пути для отправки запроса. options.url переопределяет это значение, если присутствует.
optionsObjectНетОбъект с параметрами запроса.
returnsPromisePromise, который разрешается в данные ответа после того, как они были обработаны методами 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 будет отклонен. Предоставление callback 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, и компонент перерисовывается, отображая список <div> с заголовками каждого todo.

Обработка ошибок ​

Когда запрос (не 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

Прерывание запросов ​

Иногда желательно прервать запрос. Например, в виджете автозаполнения/typeahead вы хотите убедиться, что завершается только последний запрос, потому что обычно автозаполнители запускают несколько запросов по мере ввода пользователем, и HTTP-запросы могут завершаться не по порядку из-за непредсказуемой природы сетей. Если другой запрос завершается после последнего запущенного запроса, виджет будет отображать менее релевантные (или потенциально неправильные) данные, чем если бы последний запущенный запрос завершился последним.

m.request() предоставляет свой базовый объект XMLHttpRequest через параметр options.config, который позволяет вам сохранить ссылку на этот объект и вызвать его метод 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 для создания многокомпонентного запроса, который является специально отформатированным HTTP-запросом, который может отправлять данные файла в теле запроса.

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

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

Далее вам нужно вызвать m.request и установить для options.method 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,
  });
}

Предполагая, что сервер настроен на прием многокомпонентных запросов, информация о файле будет связана с ключом myfile.

Загрузка нескольких файлов ​

Можно загрузить несколько файлов в одном запросе. Это сделает пакетную загрузку атомарной, т.е. никакие файлы не будут обработаны, если во время загрузки произойдет ошибка, поэтому невозможно сохранить только часть файлов. Если вы хотите сохранить как можно больше файлов в случае сбоя сети, вам следует рассмотреть возможность загрузки каждого файла в отдельном запросе.

Чтобы загрузить несколько файлов, просто добавьте их все в объект 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() предоставляет свой базовый объект XMLHttpRequest через параметр options.config, который позволяет вам прикреплять прослушиватели событий к объекту 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, что данные изменились и требуется повторная отрисовка
      });
    },
  });
}

В приведенном выше примере отображается поле выбора файла. Если пользователь выбирает файл, инициируется загрузка, и в callback config регистрируется обработчик событий progress. Этот обработчик событий срабатывает всякий раз, когда происходит обновление прогресса в XMLHttpRequest. Поскольку событие прогресса XMLHttpRequest не обрабатывается напрямую движком виртуального DOM Mithril.js, необходимо вызвать 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 обеспечивает парадигму разработки пользовательского интерфейса удержанного режима, что значительно упрощает разработку и обслуживание сложных взаимодействий с пользователем.

По умолчанию m.request ожидает, что данные ответа будут в формате JSON. В типичном приложении Mithril.js эти данные JSON обычно используются представлением.

Следует избегать попыток отображения динамического HTML, сгенерированного сервером, с помощью Mithril. Если у вас есть существующее приложение, которое использует систему шаблонов на стороне сервера, и вы хотите изменить его архитектуру, сначала решите, возможно ли это вообще. Переход от архитектуры толстого сервера к архитектуре толстого клиента обычно требует значительных усилий и включает в себя рефакторинг логики из шаблонов в логические службы данных (и тестирование, которое с этим связано).

Службы данных могут быть организованы по-разному в зависимости от характера приложения. RESTful архитектуры популярны среди поставщиков API, а сервис-ориентированные архитектуры часто требуются там, где существует множество высокотранзакционных рабочих процессов.

Почему XHR вместо fetch ​

fetch() - это более новый Web API для получения ресурсов с серверов, аналогичный XMLHttpRequest.

m.request Mithril.js использует XMLHttpRequest вместо fetch() по ряду причин:

  • fetch еще не полностью стандартизирован и может быть подвержен изменениям спецификации.
  • Вызовы XMLHttpRequest можно прервать до их разрешения (например, чтобы избежать гонок данных в пользовательских интерфейсах мгновенного поиска).
  • XMLHttpRequest предоставляет хуки для прослушивателей прогресса для длительных запросов (например, загрузка файлов).
  • XMLHttpRequest поддерживается всеми браузерами, тогда как fetch() не поддерживается Internet Explorer и более старыми версиями Android (до 5.0 Lollipop).

В настоящее время из-за отсутствия поддержки браузерами fetch() обычно требует polyfill, который имеет размер более 11 КБ в несжатом виде - почти в три раза больше, чем модуль XHR Mithril.js.

Несмотря на то, что модуль XHR Mithril.js намного меньше, он поддерживает множество важных и нетривиальных в реализации функций, таких как интерполяция URL и сериализация строки запроса, в дополнение к своей способности беспрепятственно интегрироваться с подсистемой автоматической перерисовки Mithril.js. Polyfill fetch не поддерживает ни одну из этих функций и требует дополнительных библиотек и шаблонов для достижения того же уровня функциональности.

Кроме того, модуль XHR Mithril.js оптимизирован для конечных точек на основе JSON и делает этот наиболее распространенный случай подходящим образом кратким - т.е. m.request(url) - тогда как fetch требует дополнительного явного шага для анализа данных ответа как JSON: fetch(url).then(function(response) {return response.json()})

API fetch() имеет несколько технических преимуществ перед XMLHttpRequest в нескольких необычных случаях:

  • он предоставляет потоковый API (в смысле "потоковой передачи видео", а не в смысле реактивного программирования), который обеспечивает лучшую задержку и потребление памяти для очень больших ответов (за счет сложности кода).
  • он интегрируется с Service Worker API, который обеспечивает дополнительный уровень контроля над тем, как и когда происходят сетевые запросы. Этот API также предоставляет доступ к push-уведомлениям и функциям фоновой синхронизации.

В типичных сценариях потоковая передача не обеспечит заметных преимуществ в производительности, потому что, как правило, не рекомендуется загружать мегабайты данных. Кроме того, выигрыш в памяти от многократного повторного использования небольших буферов может быть компенсирован или аннулирован, если они приведут к чрезмерным перерисовкам браузера. По этим причинам выбор потоковой передачи fetch() вместо m.request рекомендуется только для приложений, требующих чрезвычайно больших ресурсов.

Избегайте анти-паттернов ​

Promises - это не данные ответа ​

Метод m.request возвращает Promise, а не сами данные ответа. Он не может вернуть эти данные напрямую, потому что HTTP-запрос может занять много времени (из-за задержки сети), и если бы JavaScript ждал его, он бы заморозил приложение до тех пор, пока данные не станут доступны.

javascript
// ИЗБЕГАТЬ
var users = m.request('/api/v1/users');
console.log('list of users:', users);
// `users` - это НЕ список пользователей, это промис

// Предпочтительный вариант
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