Меню

Обработка ошибок js фетч пост запросов

В моей программе, я вывожу forks репозитория, который вбиваю вручную.

Естественно хочу, в случае, если допущена ошибка ввода, обработать ее, например, вывести сообщение, что данный репозиторий не найден, и чтобы в консоли не было никаких сообщений об ней.

Вот так выглядит рабочий пример без обработки ошибок:

fetch(`https://api.github.com/repos/gudh/ihover/forks`)
    .then(res => res.json())
    .then(data => console.log('+', data))

А вот здесь я пытаюсь обработать ошибку, чтобы понять, где мне писать код, который будет выводить сообщение о том, что не найден репозиторий, я вызываю console.log, но ни .catch ни try не вызывают console.log, и все равно ошибка падает в консоль.

try {
    fetch(`https://api.github.com/repos/gudh/ihove/forks`)
        .then(res => res.json())
        .then(data => console.log('+', data))
        .catch(() => console.log(12));
} catch (e) {
    console.log(132)
}

Если откроете консоль браузера, увидите, что туда попала ошибка:

введите сюда описание изображения

I really liked @tjvantoll article Handling Failed HTTP Responses With fetch(). The one thing I found annoying with it, though, is that response.statusText always returns the generic error message associated with the error code. Most APIs, however, will generally return some kind of useful, more human friendly message in the body.

Here’s a modification that will capture this message. The key is that rather than throwing an error, you just throw the response and then process it in the catch block to extract the message in the body:

fetch("/api/foo")
  .then( response => {
    if (!response.ok) { throw response }
    return response.json()  //we only get here if there is no error
  })
  .then( json => {
    this.props.dispatch(doSomethingWithResult(json)) 
  })
  .catch( err => {
    err.text().then( errorMessage => {
      this.props.dispatch(displayTheError(errorMessage))
    })
  })

Frankly, I’m horrified that JavaScript let’s you throw some random value, rather than an error, but hey, when in Rome…

Fetch API — это инструмент для выполнения сетевых запросов в веб-приложениях. При базовом использовании fetch() — довольно простой метод, но у него есть много нюансов. Например, мы не можем прервать fetch-запрос.

В этой статье рассмотрим сценарии использования fetch() вместе с другой возможностью языка — синтаксисом async/await. Разберемся, как получать данные, обрабатывать ошибки и отменять запросы.

Fetch API позволяет делать HTTP-запросы (GET, POST и т. д.) и обмениваться данными с сервером. Это более удобный аналог XHR.

Чтобы выполнить запрос, просто вызовите функцию fetch():

const response = await fetch(resource[, options]);
  • Первый параметр resource — это URL-адрес запроса и объект Request.
  • Второй (необязательный) параметр options — это конфигурация запроса. Можно настроить method, header, body, credentials и другие опции.

Функция fetch выполняет запрос и возвращает промис, который будет ждать, когда запрос завершится. После этого промис выполняется (resolve) с объектом Response (ответ сервера). Если во время запроса произошла какая-то ошибка, промис переходит в состояние rejected.

Синтаксис async/await прекрасно сочетается с fetch() и помогает упростить работу с промисами. Давайте для примера сделаем запрос списка фильмов:

async function fetchMovies() {
  const response = await fetch('/movies');
  // ждем выполнения запроса
  console.log(response);
}

Функция fetchMovies асинхронная, используем для ее создания ключевое слово async. Внутри она использует await, чтобы дождаться выполнения асинхронной операции fetch.

Внутри функции выполняется запрос на урл /movies. Когда он успешно завершается, мы получаем объект response с ответом сервера. Дальше в статье мы разберемся, как извлечь данные из этого объекта.

Получение JSON

Из объекта response, который возвращается из await fetch() можно извлечь данные в нескольких разных форматах. Чаще всего используется JSON:

async function fetchMoviesJSON() {
  const response = await fetch('/movies');
  const movies = await response.json();
  return movies;
}
fetchMoviesJSON().then(movies => {
  movies; // полученный список фильмов
});

Итак, чтобы извлечь полученные данные в виде JSON, нужно использовать метод response.json(). Этот метод возвращает промис, так что придется снова воспользоваться синтаксисом await, чтобы дождаться его выполнения: await response.json().

Кроме того, у объекта Response есть еще несколько полезных методов (все методы возвращают промисы):

  • response.json() возвращает промис, который резолвится в JSON-объект;
  • response.text() возвращает промис, который резолвится в обычный текст;
  • response.formData() возвращает промис, который резолвится в объект FormData;
  • response.blob() возвращает промис, который резолвится в Blob (файлоподобный объект с необработанными данными);
  • response.arrayBuffer()() возвращает промис, который резолвится в ArrayBuffer (необработанные двоичные данные).

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

Для разработчиков, которые только начинают работать с fetch, может быть непривычным то, что этот метод не выбрасывает исключение, если сервер возвращает «плохой» HTTP-статус (клиентские 400-499 или серверные 500-599 ошибки).

Попробуем для примера обратиться к несуществующей странице /oops. Этот запрос, как и ожидается, завершается со статусом 404.

async function fetchMovies404() {
  const response = await fetch('/oops');
  
  response.ok;     // => false
  response.status; // => 404
  const text = await response.text();
  return text;
}
fetchMovies404().then(text => {
  text; // => 'Page not found'
});

Из объекта response мы можем узнать, что запрос не удался, однако метод fetch() не выбрасывает ошибку, а считает этот запрос завершенным.

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

Но к счастью, у нас есть поле response.ok, с помощью которого мы можем отловить плохие статусы. Оно принимает значение true только если статус ответа 200-299.

Если вы хотите получать ошибку для всех неудачных запросов, просто выбрасывайте ее вручную:

async function fetchMoviesBadStatus() {
  const response = await fetch('/oops');
  if (!response.ok) {
    const message = `An error has occured: ${response.status}`;
    throw new Error(message);
  }
  const movies = await response.json();
  return movies;
}
fetchMoviesBadStatus().catch(error => {
  error.message; // 'An error has occurred: 404'
});

Отмена fetch-запроса

К сожалению, Fetch API не предоставляет нам никакой возможности отменить запущенный запрос. Но это можно сделать с помощью AbortController.

Чтобы объединить эти инструменты, нужно сделать 3 действия:

// Шаг 1. Создать экземпляр AbortController до начала запроса
const controller = new AbortController();

// Step 2: Передать в параметры запроса fetch() controller.signal
fetch(..., { signal: controller.signal });

// Step 3: Отменить запрос методом controller.abort при необходимости
controller.abort();

Давайте для примера создадим маленькое приложение с двумя кнопками, одна из которых будет запускать fetch-запрос, а вторая прерывать его.

let controller = null;

// Обрабатываем клики по первой кнопке
fetchMoviesButton.addEventListener('click', async () => {
  controller = new AbortController();
  try {
    const response = await fetch('/movies', { 
      signal: controller.signal 
    });
  } catch (error) {
    console.log('Fetch error: ', error);
  }
  controller = null;
});

// Обрабатываем клики по второй кнопке
cancelFetchButton.addEventListener('click', () => {
  if (controller) {
    controller.abort();
  }
});

Демо:

Кликните по кнопке Fetch movies, чтобы запустить запрос, а затем по кнопке Cancel fetch, чтобы отменить его. При этом возникнет ошибка, которую поймает блок .catch().

Экземпляр AbortController одноразовый, его нельзя переиспользовать для нескольких запросов. Поэтому для каждого вызова fetch нужно создавать новый инстанс.

  • Как прервать fetch-запрос, если он не завершился через определенное время

Параллельные fetch запросы

Чтобы выполнять fetch-запросы параллельно, можно воспользоваться методом Promise.all().

Например, запустим сразу два запроса — для получения фильмов и для получения категорий фильмов:

async function fetchMoviesAndCategories() {
  const [moviesResponse, categoriesResponse] = await Promise.all([
    fetch('/movies'),
    fetch('/categories')
  ]);
  const movies = await moviesResponse.json();
  const categories = await categoriesResponse.json();
  return [movies, categories];
}
fetchMoviesAndCategories().then(([movies, categories]) => {
  movies;     // список фильмов
  categories; // список категорий
}).catch(error => {
  // один из запросов завершился с ошибкой
});

Фрагмент кода await Promise.all([]) запускает запросы параллельно и ожидает, когда все они перейдут в состояние resolved.

Если один из запросов завершится с ошибкой, то Promise.all тоже выбросит ошибку.

Если же вы хотите, чтобы выполнились все запросы, даже если несколько из них упали, используйте метод Promise.allSettled().

Заключение

Вызов функции fetch() запускает запрос к серверу и возвращает промис. Когда запрос успешно завершается, промис переходит в состояние resolved и возвращает объект ответа (response), из которого можно извлечь данные в одном из доступных форматов (JSON, необработанный текст или Blob).

Так как fetch возвращает промис, мы можем использовать синтаксис async/await, чтобы упростить код: response = await fetch().

В статье мы разобрали, как использовать эту комбинацию для получения данных, обработки ошибок, отмены запросов и выполнения параллельных запросов.

Если вы пришли сюда только ради ответа и вам не интересны рассуждения — листайте вниз 🙂

Как все начиналось

Для начала, давайте вспомним, а как вообще ловят ошибки в js, будь то браузер или сервер. В js есть конструкция try...catch.

try {
    let data = JSON.parse('...');
} catch(err: any) {
		// если произойдет ошибка, то мы окажемся здесь
}

Это общепринятая конструкция и в большинстве языков она есть. Однако, тут есть проблема (и как окажется дальше — не единственная), эта конструкция «не будет работать» для асинхронного кода, для кода который был лет 5 назад. В те времена, в браузере использовали для Ajax запроса XMLHttpRequest.

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com', true);
xhr.addEventListener('error', (e: ProgressEvent<XMLHttpRequestEventTarget>) => {
    // если произойдет ошибка, то мы окажемся здесь
});

Тут используется механизм подписки на событие возникновения ошибки. В данном случае, переменная e является событием, фактически мы ушли от настоящей ошибки и закрылись некоторой абстракцией, за которой спрятана настоящая ошибка, доступа к которой у нас нет.

В NodeJS с самого начала продвигалась концепция Error-First Callback, эта идея применялась для асинхронных функций, например, для чтения файла. Смысл ее в том, чтобы первым аргументом передавать в функцию обратного вызова ошибку, а следующими аргументами уже получаемые данные.

import fs from 'fs';

fs.readFile('file.txt', (err, data) => {
    if (err) {
        // обработка ошибки
    }
    // если все хорошо, работаем с данными
});

Если мы посмотрим какой тип имеет переменная err, то увидим следующее:

interface ErrnoException extends Error {
    errno?: number | undefined;
    code?: string | undefined;
    path?: string | undefined;
    syscall?: string | undefined;
}

Тут действительно находится ошибка. По сути, это тот же способ, что и выше, только в этом случает мы получаем объект Error.

Через некоторое время, в Javascript появились Promise. Они, безусловно, изменили разработку на js к лучшему. Ведь никто* никто не любит городить огромные конструкции из функций обратного вызова.

fetch('https://api.example.com')
  .then(res => {
    // если все хорошо, работаем с данными
  })
  .catch(err => {
		// обработка ошибки
  });

Несмотря на то, что внешне этот пример сильно отличается от первого, тем не менее, мы видим явную логическую связь. Очевидно, что разработчики хотели сделать похожую на try...catch конструкцию. Со временем, появился еще один способ обработать ошибку в асинхронном коде. Этот способ, по сути, является лишь синтаксическим сахаром для предыдущего примера.

try {
  const res = await fetch('https://api.example.com');
  // если все хорошо, работаем с данными
} catch(err) {
	// обработка ошибки
}

Также, конструкция try...catch позволяет ловить ошибки из нескольких промисов одновременно.

try {
  let usersRes = await fetch('https://api.example.com/users');
	let users = await usersRes.json();

  let chatsRes = await fetch('https://api.example.com/chats');
	let chats = await chatsRes.json();

  // если все хорошо, работаем с данными
} catch(err) {
	// обработка ошибки
}

Вот, замечательный вариант ловли ошибок. Любая ошибка которая возникнет внутри блока try, попадет в блок catch и мы точно её обработаем.

А точно ли обработаем?

Действительно, а правда ли, что мы обработаем ошибку, или всего лишь сделаем вид? На практике, скорее всего, возникнувшая ошибка будет просто выведена в консоль или т.п. Более того, при появлении ошибки*, интерпретатор прыгнет в блок catch , где не мы, не TypeScript не сможет вывести тип переменной, попавшей туда (пример — возврат с помощью Promise.reject), после чего, произойдет выход из функции. То есть, мы не сможем выполнить код который находится в этом же блоке, но который расположен ниже функции, внутри которой произошла ошибка. Конечно, мы можем предусмотреть такие ситуации, но сложность кода и читаемость вырастут многократно.

Как быть?

Давайте попробуем использовать подход, предлагаемый разработчиками одного небезызвестного языка.

let [users, err] = await httpGET('https://api.example.com/users');
if (err !== null) {
	// обработка ошибки
}
// продолжаем выполнение кода

Возможную ошибку мы держим всегда рядом с данными, возвращаемыми из функции, что намекает нам на то, что переменную err желательно проверить.

Пример для вызова нескольких функций возвращающих Promise.

let err: Error,
		users: User[],
		chats: Chat[];

[users, err] = await httpGET('https://api.example.com/users');
if (err !== nil) {
  // обработка ошибки
}

[chats, err] = await httpGET('https://api.example.com/chats');
if (err !== nil) {
  // обработка ошибки
}

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

Давайте рассмотрим как можно реализовать такую функцию и что нам вообще нужно делать. Для начала, давайте определим тип PairPromise. В данном случае, я решил использовать null если результата или ошибки нету, так как он просто короче.

type PairPromise<T> = Promise<[T, null] | [null, Error]>;

Определим возможные возвращаемые ошибки.

const notFoundError = new Error('NOT_FOUND');
const serviceUnavailable = new Error('SERVICE_UNAVAILABLE');

Теперь опишем нашу функцию.

const getUsers = async (): PairPromise<User[]> => {
    try {
        let res = await fetch('https://api.example.com/users');
        if (res.status === 504) {
            return Promise.resolve([null, serviceUnavailable]);
        }

        let users = await res.json() as User[];

        if (users.length === 0) {
            return Promise.resolve([null, notFoundError]);
        }

        return Promise.resolve([users, null]);
    } catch(err) {
        return Promise.resolve([null, err]);
    }
} 

Пример использования такой функции.

let [users, err] = await getUsers();
if (err !== null) {
	switch (err) {
  	case serviceUnavailable:
    	// сервис недоступен
    case notFoundError:
    	// пользователи не найдены
    default:
    	// действие при неизвестной ошибке
	}
}

Вариантов применения данного подхода обработки ошибок очень много. Мы сочетаем удобства конструкции try...catch и Error-First Callback, мы гарантированно поймаем все ошибки и сможем удобно их обработать, при необходимости. Как приятный бонус — мы не теряем типизацию. Также, мы не скованы лишь объектом Error, мы можем возвращать свои обертки и успешно их использовать, в зависимости от наших убеждений.

Очень интересно мнение сообщества на эту тему.

Fetch API используется для выполнения ajax-запросов, для вызова API или выборки удаленного ресурса или получения HTML-файла с сервера.

fetch api

Базовый синтаксис Fetch API

Допустим, нужно получить список сообщений из API https://jsonplaceholder.typicode.com/posts. Во-первых, следует передать этот URL в метод fetch() в качестве аргумента.

fetch('https://jsonplaceholder.typicode.com/posts');

Метод fetch() возвращает Promise. Поэтому ответы API нужно обрабатывать с использованием then() и catch().

fetch('https://jsonplaceholder.typicode.com/posts')
  .then(function (response) {
  // API вернул результат
  console.log('success!', response);
}).catch(function (err) {
  // Что-то пошло не так
  console.warn('Ошибка получения.');
  console.log(err);
});

Посмотрите пример.

Посмотрим на ответ в консоли: сразу можно обратить внимание, в response.body нет JSON. Это — ReadableStream.

Fetch API использует потоки. Чтобы получить данные из API в виде объекта JSON, можно использовать оригинальный метод Fetch API: json().

fetch('https://jsonplaceholder.typicode.com/posts')
  .then(function (response) {
  // API вернул результат
  return response.json();
}).then(function (data) {
  // Это уже JSON
  console.log(data);
}).catch(function (err) {
  // Что-то пошло не так
  console.warn('Ошибка получения.');
  console.log(err);
});

Посмотрите, теперь тут нормальный JSON.

Обработка ошибок для Fetch API

Fetch API обрабатывает ошибки с помощью метода catch(), потому что он возвращает Promise.

Однако, Promise отклоняется и вызывает метод catch() только в случае сбоя. Если вернулся ответ от сервера, даже в случае с ошибками 404 или 500, будут работать методы then().

Чтобы пофиксить такое поведение, можно использовать свойство ok в response, который возвращает Promise.

Если у свойства response.ok значение true, вернём response.json(). Если нет, то вернём отклоненный объект Promise и передадим в нём ответ, чтобы вызвать метод catch().

fetch('https://jsonplaceholder.typicode.com/postsQQQQ')
  .then(function (response) {
  // API вернул результат
  if (response.ok) {
    return response.json();
  } else {
    console.log('Это не 200OK')
    return Promise.reject(response);
  }
}).then(function (data) {
  // Это уже JSON
  console.log(data);
}).catch(function (err) {
  // Что-то пошло не так
  console.warn('Ошибка получения.');
  console.log(err);
});

Так лучше обрабатывать ошибки в методе Fetch.

В этом примере умышленно искажён URL, он вернёт 404 ошибку, чтобы продемонстрировать отклонение Promise вручную для принудительного вызова метода catch().

То же самое можно делать с помощью XHR

Обработчик onreadystatechange в XHR тоже выполняется независимо от того, был ответ успешным или нет. Здесь тоже нужно проверять, находится ли ответ между 200 и 300, прежде чем работать с ним.

xhr.onreadystatechange = function () {
  // Вернём, когда работа метода завершится
  if (xhr.readyState !== 4) return;
    // обработаем ответ
    if (xhr.status >= 200 && xhr.status < 300) {
      // Ответ нормальный
      console.log('success', JSON.parse(xhr.responseText));
    } else {
      // Ответ плохой
      console.log('error', xhr);
  }
};

У Fetch API код получился намного чище, сравните.

Создание других типов запросов

По умолчанию Fetch API выполняет GET-запросы. Допустим, нужно сделать POST-запрос для публикации новой статьи через API. Методу fetch() надо в этом случае передавать второй аргумент, который можно использовать для передачи объекта с параметрами.

fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST'
}).then(function (response) {
  // API вернул результат
  if (response.ok) {
    return response.json();
  } else {
    console.log('Это не 200OK')
    return Promise.reject(response);
  }
}).then(function (data) {
  // Это уже JSON
  console.log(data);
}).catch(function (err) {
  // Что-то пошло не так
  console.warn('Ошибка получения.');
  console.log(err);
});

Для определенных типов запросов может понадобиться передавать данные вместе с запросом. Это можно сделать с помощью свойства body в объекте параметров.

fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST',
  body: 'title=' + encodeURIComponent('Заголовок') +
        '&body=' + encodeURIComponent('Текст статьи')
}).then(function (response) {
  // API вернул результат
  if (response.ok) {
    return response.json();
  } else {
    console.log('Это не 200OK')
    return Promise.reject(response);
  }
}).then(function (data) {
  // Это уже JSON
  console.log(data);
}).catch(function (err) {
  // Что-то пошло не так
  console.warn('Ошибка получения.');
  console.log(err);
});

Установка заголовков с помощью Fetch API

Еще одна распространенная вещь, которую может потребоваться сделать, установить для запроса заголовки и другие свойства. Это также можно сделать с помощью объекта параметров.

fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST',
  body: 'title=' + encodeURIComponent('Заголовок') +
        '&body=' + encodeURIComponent('Текст статьи'),
  headers: {
    'Content-Type': 'application/json'
  },
  referrer: 'no-referrer'
}).then(function (response) {
  // API вернул результат
  if (response.ok) {
    return response.json();
  } else {
    console.log('Это не 200OK')
    return Promise.reject(response);
  }
}).then(function (data) {
  // Это уже JSON
  console.log(data);
}).catch(function (err) {
  // Что-то пошло не так
  console.warn('Ошибка получения.');
  console.log(err);
});

Демка

Здесь можно поэкспериментировать с кросс-доменными запросами (CORS).

See this code cors fetch on x.xhtml.ru.

How to use the Fetch API with vanilla JS

Начало: «JavaScript: простой пример работы с методом fetch».

В прошлом посте я разбирал, как заставить работать пример с встроенным методом fetch из подраздела 3.1 «Fetch» третьего раздела («Сетевые запросы») третьей части («Тематические разделы») учебника по JavaScript.

В итоге у меня получился такой код:

(async () => {
    let url = "https://api.github.com";
    let response = await fetch(url);

    if (response.ok) { // если HTTP-статус в диапазоне 200-299
        // получаем тело ответа
        let json = await response.json();
        console.log(json);
    } else {
        alert("Ошибка HTTP: " + response.status);
    }
})();

Можно было заметить, что, несмотря на вроде бы наличие ветки с обработкой ошибок, скрипт всё равно при некоторых ошибках (эти ошибки в консоли разработчика содержат в своём названии слово «Uncaught», что по-русски означает «Непойманная» [в смысле «непойманная ошибка»]) прекращает работу. (Я писал об этом отдельный пост: «JavaScript: слово Uncaught в названии ошибки».)

На самом деле, в вышеприведенном коде ветка с обработкой ошибок обрабатывает только ошибки, которые возникнут на веб-сервере при обработке запроса, который мы в данном коде посылаем на веб-сервер (ошибкой наш скрипт считает все ответы веб-сервера с кодом, выходящим за пределы диапазона 200-299).

Ну а непойманная ошибка в данном случае может возникнуть до того, как исполнение скрипта дойдет до ветки с обработкой ошибок, при выполнении одной из двух асинхронных задач. Либо тут (выполнение первой асинхронной задачи):

    let response = await fetch(url);

либо тут (выполнение второй асинхронной задачи):

        let json = await response.json();

Если случается непойманная скриптом ошибка, скрипт прекращает работу. При обучении или отладке мы временно можем себе это позволить, так как у нас при этом есть более важные задачи: при обучении более важная задача — это понять иллюстрируемые данным примером кода некие принципы программирования или решение каких-то задач (например, научиться работать с встроенным методом fetch, как в обсуждаемой статье учебника); а при отладке более важная задача — заставить код сначала хоть как-то работать.

Но в скрипте, как окончательном продукте, мы не можем себе позволить непойманных ошибок. Ведь в скрипте при возникновении непойманной ошибки могут еще быть невыполненные инструкции, то есть скрипт на момент такой ошибки, возможно, еще не успел сделать какие-то важные действия. Пользователю при этом никаких ошибок не показывается, скрипт просто «умирает» (если пользователь продвинутый, он, конечно, может открыть консоль разработчика в браузере [если скрипт был запущен в браузерном окружении] и увидеть сообщение о непойманной ошибке). Так что все возможные непойманные ошибки должны быть отловлены.

Как это сделать в данном случае? Изменим код (я отметил изменение красным цветом):

(async () => {
    let url = "https://api.github.com";
    let response = await fetch(url);

    if (response.ok) { // если HTTP-статус в диапазоне 200-299
        // получаем тело ответа
        let json = await response.json();
        console.log(json);
    } else {
        alert("Ошибка HTTP: " + response.status);
    }
})().catch(error => alert(error));

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

Подробнее об обработке ошибок при работе с промисами можно почитать следующие статьи учебника из первой части учебника:

Потребители: then, catch, finally
https://learn.javascript.ru/promise-basics#potrebiteli-then-catch-finally

Промисы: обработка ошибок
https://learn.javascript.ru/promise-error-handling

Обработка ошибок при использовании синтаксиса «async/await»
https://learn.javascript.ru/async-await#obrabotka-oshibok

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

А вот еще интересные материалы:

  • Яшка сломя голову остановился исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного где ошибка
  • Обработка ошибок i2c stm32
  • Обработка ошибок file get contents