Одна функция для Ajax-запросов. Как вернуть данные

Долго сидел и думал, какое же дать название этой статье, т.к. тема гораздо обширнее, чем просто "вернуть ответ из функции Ajax-запросов". Сюда можно было бы приклеить и "Работа с полученным ответом на Ajax-запрос", и "Универсальная функция Ajax-запросов", и "Работа с объектами Promise и Deferred" , и еще массу вариантов. Но всё это сводится к одной проблеме, которая изо дня в день поднимается новичками на форумах и которую можно определить одной фразой - асинхронность выполнения Ajax-запросов. Что же вообще это значит: "синхронное или асинхронное выполнение"? Если не особо углубляясь и, как говорится, на пальцах, то: Синхронное выполнение - это, когда вы сели на горшок справлять нужду, то штаны не натягиваете, пока не завершите этот процесс, а потом не воспользуетесь туалетной бумагой, а может быть еще и биде. ;) Асинхронное выполнение - это, когда вы поставили чайник на огонь, включили стиральную машинку и сели читать книгу. Все процессы выполняются независимо друг от друга и не дожидаясь окончания одним другого. Надеюсь, что суть уловили. :) Давайте разберем типичный ошибочный пример:

function ajaxRequest() {
  var response = false;
  $.ajax({
    url: '/path/to/handler.php',
    /* прочие настройки */
    success: function (data) {
       response = data;
    }
  });
  return response;
}
var something = ajaxRequest();

И вопрос обычно звучит в таком стиле: "Почему функция не возвращает данные? Почему всегда false?" Теперь уже, когда вы знаете разницу между синхронным и асинхронным выполнением, вы можете без труда понять, что происходит внутри функци "ajaxRequest": инициализировали переменную "response", начался процесс ajax-запроса, но еще до его завершения, функция уже возвращает значение переменной, которое так и не успело изменится. Как с этим бороться? Первое, что может прийти на ум и будет решение не таким уж правильным - это сделать запрос синхронным. В методе $.ajax(), за это отвечает параметр async со значение false (по умолчанию - значение true). Главный минус такого подхода в том, что на время выполнения запроса, сайт у пользователя попросту "подвиснет", а это не есть хорошо. Кроме того, это может отрицательно сказаться на выполнении каких-либо фоновых операциях. Например, если у вас есть функция, которая каждую секунду должна что-то выполнять, то на время Ajax-запроса, она так же зависнет. Поэтому синхронное выполнение, мы отложим для особых случаев, где без синхронности обойтись нельзя. Универсальная функция для Ajax - это хорошо, но не всегда такую функцию можно сделать под конкретный проект, а понятие "универсальность" в широком смысле, тут вообще не подходит. Как минимум, ответ обрабатывается по разному, запросы могут передавать как обычные данные, так и файлы, где настройки запроса отличаются, могут отличаться функции beforeSend, complete и т.д. В этом случае, можно упростить код за счет метода $.ajaxSetup(). Основные настройки или настройки, которые будут действовать в большинстве случаев, мы можем указать в коде один раз и больше не прописывать их в методе $.ajax().

$.ajaxSetup({
  url: '/path/to/handler.php',
  type: 'POST',
  dataType: 'json',
  beforeSend: function() {
    $('.loader').show();
  },
  complete: function() {
    $('.loader').hide();
  },
  error: function(jqXHR, textStatus, errorThrown) {
    console.log('Ошибка: ' + textStatus + ' | ' + errorThrown);
  }
});

Теперь в любом месте, где нужно сделать ajax-запрос, мы прописываем только то, что отправляем и обработчик ответа:

$.ajax({
  data: {val1: 1, val2: 2},
  success: function(data){
     // обрабатываем ответ
  }
});

Если же в каком-то исключительном случае, нам нужно изменить определенную настройку, то её так же дописываем. Например, в каком-то случае, нам не нужно показывать "loader" ( GIF-прелоадер ) перед отправкой запроса или вместо loader-а, вывести что-то в консоль:

$.ajax({
  beforeSend: false,
  data: {val1: 1, val2: 2},
  success: function(data){
     // обрабатываем ответ
  }
});
$.ajax({
  beforeSend: function(){
    console.log('Запрос стартует');
  },
  data: {val1: 1, val2: 2},
  success: function(data){
     // обрабатываем ответ
  }
});

Это был один из вариантов, с помощью которого можно сделать код более универсальным. Теперь же перейдём непосредственно к функциям. Вариант первый - передача в функцию обработчика ответа на Ajax-запрос:

var mainContent = $('#main'),
  sidebarContent = $('#sidebar'),
  actions = {
    updateContent: function (response) {
      mainContent.html(response);
    },
    updateSidebar: function (response) {
      sidebarContent.html(response);
    }
  };

function ajaxRequest(data, responseHandler) {
  $.ajax({
    url: '/handler.php',
    type: 'POST',
    data: {
      upd: data
    },
    success: actions[responseHandler]
  });
}


ajaxRequest(1,'updateContent'); // вызов функции на обновление элемента #main
ajaxRequest(2,'updateSidebar'); // вызов функции на обновление элемента #sidebar

Итак, предположим, что у нас на странице есть два элемента, объект "actions", содержащий функции для обработки данных ответа и одна функция ajax-запросов, которая принимает два аргумента: "data" - какие-то данные, передаваемые на сервер и "responseHandler" - имя функции, которая должна обработать ответ сервера (аргументов, понятное дело, что может быть и больше). После успешного завершения запроса, вызывается соответствующая функция из объекта "actions", которая принимает полученные от сервера данные (переменная "response") и заменяет ими контент соответствующего элемента. Если нам понадобится еще какой-то обработчик, то мы просто допишем в объект "actions" новую функцию, при этом что-либо изменять дополнительно уже не понадобится. В примере, мы вручную записывали имя функции-обработчика, но это можно так же автоматизировать. Например, используя атрибут тегов "data-*": HTML

<button class="update-something" data-action="updateContent" data-id="1">Обновить контент</button>
<button class="update-something" data-action="updateSidebar" data-id="2">Обновить сайдбар</button>

JS/jQuery

$('.update-something').on('click', function(){
  var $that = $(this);
  ajaxRequest($that.data('id'), $that.data('action'));
});

В итоге, у нас одна функция для Ajax-запросов, один расширяемый объект с обработчиками, один обработчик кликов для всех кнопок, а вот результат разный и тот, который нужен нам ;) Вариант второй - используем JavaScript-объект Promise ("обещание"), которой служит для отложенных и асинхронных вычислений. Проще говоря, с помощью этого объекта мы можем установить обработчик события на выполнение какой-либо задачи. В jQuery существует свой объект для работы с "обещаниями" - это объект $.Deferred(). Разбирать его по косточкам не будем, т.к. все можно найти в документации, а просто рассмотрим пару вариантов использования и один из основных методов объекта Deffered - $.when(). Изменим наш код выше таким образом:

function ajaxRequest(data) {
  return $.ajax({
    url: '/handler.php',
    type: 'POST',
    data: {
      upd: data
    }
  });
}

$('.some-button').on('click', function(){

  $.when( ajaxRequest($(this).data('somedata')) ).then(
    function (response) {
      console.log(response); // данные ответа с сервера
    },
    function (error) {
      console.log(error.statusText); // текст ошибки
    }
  );
  
});

Объект с обработчиками нам уже не нужен, т.к. мы обрабатываем ответ сервера на месте. Опции success, error и т.д. - нам так же не нужны, так как метод $.when(), создавая новый deferred-объект, следит за состоянием процесса. А в методе .then(), мы устанавливаем обработчики событий: doneCallbacks - функция, которая будет вызвана в случае успешно выполнения failCallbacks - функция вызываемая при возникновении ошибки И, при желании, progressCallbacks - обработчик события "progress" Кому-то может показаться такой подход малополезным, но представим ситуацию, когда вам нужно сделать два, три или более ajax-запросов, дождаться завершения каждого, обработать ответы и только потом вывести на экран. Любители "индусского кода" не растеряются :) Они напишут тонну лишнего кода, в то время, как это можно сделать достаточно легко с помощью того же метода $.when():

function ajaxRequest(data) {
  return $.ajax({
    url: '/handler.php',
    type: 'POST',
    data: {
      upd: data
    }
  });
}

$('.some-button').on('click', function(){

  $.when( 
    ajaxRequest('somedata1'),
    ajaxRequest('somedata2'),
    ajaxRequest('somedata3')
  ).then(
    function (r1, r2, r3) {
      console.log(r1[0]); // данные ответа первого ajax-запроса
      console.log(r2[0]); // второго ajax-запроса
      console.log(r3[0]); // третьего ajax-запроса
    }
  );
  
});

Функция Ajax осталась одна, ничего лишнего писать не пришлось и результат тот, которого мы ожидали. И напоследок, покажу, как можно использовать Promise на чистом JavaScript. Сам код Ajax-запроса я описывать тут не буду, т.к. я его уже разбирал в статье Запрос на чистом JavaScript, а схематически это будет выглядеть так:

function ajaxRequest(data) {
  return new Promise(function(resolve, reject) {
  
    /* прочий код Ajax-запроса */
    xhr.onreadystatechange = function() {
      if(xhr.readyState == 4 && xhr.status == 200){
        // вызываем callback функцию успешного выполнения, 
        // передавая полученный ответ
        return resolve(xhr.responseText);
      }
    }
    
  });
}

document.querySelector('.some-button').addEventListener('click', function(){
  ajaxRequest('somedata').then(
    function(response){
      console.log(response); // данные ответа ajax-запроса
    }
  );
}, false);

Надеюсь, что этот краткий обзор, поможет новичкам сориентироваться в правильном направлении. Более подробно про функции объекта Deferred - читайте в официальной документации ;)

02.03.2023

164
A B i U S JS

PHP HTML CSS
Чат
    Для входа только имэйл или имя и апроль
    Можно сменить аватар
    Имэйл Ваше имя
    Пароль