На этой странице

Undici — это библиотека HTTP-клиента, на которой работает fetch API в Node.js. Она написана с нуля и не опирается на встроенный HTTP-клиент Node.js. В ней есть ряд возможностей, которые делают её хорошим выбором для высокопроизводительных приложений.

Информацию о соответствии Undici спецификации см. в документации Undici.

async function main() {
  // Как и в браузерном fetch API, метод по умолчанию — GET
  const response = await fetch('https://jsonplaceholder.typicode.com/posts');
  const data = await response.json();
  console.log(data);
  // вернёт что-то вроде:
  //   {
  //   userId: 1,
  //   id: 1,
  //   title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
  //   body: 'quia et suscipit\n' +
  //     'suscipit recusandae consequuntur expedita et cum\n' +
  //     'reprehenderit molestiae ut ut quas totam\n' +
  //     'nostrum rerum est autem sunt rem eveniet architecto'
  // }
}

main().catch(console.error);
// Данные, отправляемые с клиента на сервер
const body = {
  title: 'foo',
  body: 'bar',
  userId: 1,
};

async function main() {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'POST',
    headers: {
      'User-Agent': 'undici-stream-example',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });
  const data = await response.json();
  console.log(data);
  // вернёт что-то вроде:
  // { title: 'foo', body: 'bar', userId: 1, id: 101 }
}

main().catch(console.error);

Undici позволяет настраивать Fetch API, передавая опции в функцию fetch. Например, можно задать пользовательские заголовки, указать метод запроса и тело запроса. Вот пример того, как настроить Fetch API с помощью Undici:

Функция fetch принимает два аргумента: URL для запроса и объект опций. Объект опций — это объект настроек, с помощью которого можно настроить запрос. Функция возвращает Promise, который резолвится в объект Response.

В следующем примере мы отправляем POST-запрос к API Ollama с JSON-полезной нагрузкой. Ollama — это CLI-инструмент, позволяющий запускать LLM (большие языковые модели) на локальной машине. Скачать его можно здесь.

Эта команда скачает модель mistral и запустит её на вашей локальной машине.

С помощью пула (pool) можно переиспользовать соединения с одним и тем же сервером, что повышает производительность. Вот пример использования пула с Undici:

import { Pool } from 'undici';

const ollamaPool = new Pool('http://localhost:11434', {
  connections: 10,
});

/**
 * Стримит завершение (completion) промпта через API Ollama.
 * @param {string} prompt - Промпт, который нужно завершить.
 * @link https://github.com/ollama/ollama/blob/main/docs/api.md
 **/
async function streamOllamaCompletion(prompt) {
  const { statusCode, body } = await ollamaPool.request({
    path: '/api/generate',
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ prompt, model: 'mistral' }),
  });

  // Про коды состояния HTTP можно почитать здесь: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
  // 200 означает, что запрос выполнен успешно.
  if (statusCode !== 200) {
    // Тело ответа обязательно нужно вычитать: https://undici.nodejs.org/#/?id=garbage-collection
    await body.dump();
    throw new Error(`Ollama request failed with status ${statusCode}`);
  }

  let partial = '';

  const decoder = new TextDecoder();
  for await (const chunk of body) {
    partial += decoder.decode(chunk, { stream: true });
    console.log(partial);
  }

  console.log('Streaming complete.');
}

try {
  await streamOllamaCompletion('What is recursion?');
} catch (error) {
  console.error('Error calling Ollama:', error);
} finally {
  console.log('Closing Ollama pool.');
  ollamaPool.close();
}

Потоки (streams) — это возможность Node.js, позволяющая читать и записывать данные по частям (чанкам).

import { Writable } from 'node:stream';

import { stream } from 'undici';

async function fetchGitHubRepos() {
  const url = 'https://api.github.com/users/nodejs/repos';

  await stream(
    url,
    {
      method: 'GET',
      headers: {
        'User-Agent': 'undici-stream-example',
        Accept: 'application/json',
      },
    },
    res => {
      let buffer = '';

      return new Writable({
        write(chunk, encoding, callback) {
          buffer += chunk.toString();
          callback();
        },
        final(callback) {
          try {
            const json = JSON.parse(buffer);
            console.log(
              'Repository Names:',
              json.map(repo => repo.name)
            );
          } catch (error) {
            console.error('Error parsing JSON:', error);
          }
          console.log('Stream processing completed.');
          console.log(`Response status: ${res.statusCode}`);
          callback();
        },
      });
    }
  );
}

fetchGitHubRepos().catch(console.error);