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

Два важных аспекта использования обратного вызова в JavaScript.

  • Асинхронная операция:
    Любая функция/задача, выполнение которой требует некоторого времени, обратный вызов гарантирует, что она не блокирует основной поток и обеспечивает отзывчивость нашей страницы, а выполнение задач занимает много времени. за кулисами. как только он завершится, мы сможем использовать результат.
  • Последовательная операция:
    Если у нас есть более одной асинхронной операции и мы хотим выполнить ее последовательно, поскольку результат одной задачи определяет результат другой задачи.

Давайте разберемся на каком-нибудь примере.

Мы возьмем одну проблему и попытаемся решить ее с помощью обратного вызова, обещания и метода async-await, предоставляемого JavaScript.

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

// as setTimeout is asynchronous operation which requires a function to execute as callback. 
// It will not wait for the taken execution time. Once it finishes after 2 second, output will show

function getData(){
 console.log("data fetched successfully")
}

console.log("Start of the main program");
setTimeout(getData, 2000);
console.log("End of the main program");

// "Start of the main program"
// "End of the main program"
// "data fetched successfully"

Теперь рассмотрим последовательный подход к решению асинхронной задачи.

Итак, диаграмма ниже — это наша проблема.

Идентификатор → Сведения о пользователе → Сведения о репо → Сведения о фиксации

Это список асинхронных задач, которые выполняются в одном потоке.

// Synchronous operation 

function task1(id){
  let userDetails= {id, userGithubName: "bittu"+id}
  return userDetails;
}

function task2(userGithubName){
  let repoDetails= {userGithubName, repos:['repo1', 'repo2', 'repo3'] }
  return repoDetails;
}

function task3(repoName){
  let commitDetails= {repoName, commits:['commit1', 'commit2','commit3' ]}
  return commitDetails;
}

let task1result= task1(2);
let task2result= task2(task1result.userGithubName);
let task3Result= task3(task2result.repos[0]);

console.log(task3Result.commits)

// ['commit1', 'commit2', 'commit3']

Теперь выполнение каждой задачи занимает некоторое время, поместим setTimeout для каждой задачи и посмотрим. (создание асинхронного характера с использованием setTimeout)

// asynchronous task

function task1(id){
setTimeout(()=>{
 let userDetails= {id, userGithubName: "bittu"+id}
  return userDetails;
  }, 1000)
}

function task2(userGithubName){
setTimeout(()=>{
  let repoDetails= {userGithubName, repos:['repo1', 'repo2', 'repo3'] }
  return repoDetails;
  }, 2000)
}

function task3(repoName){
setTimeout(()=>{
  let commitDetails= {repoName, commits:['commit1', 'commit2','commit3' ]}
  return commitDetails;
   }, 3000)
}


let task1result= task1(2);
console.log(task1result)
let task2result= task2(task1result.userGithubName);
let task3Result= task3(task2result.repos[0]);

console.log(task3Result.commits)

Итак, ошибка, которую вы видите, связана с:
Он пытается работать синхронно и в одном потоке, поскольку JavaScript по своей природе синхронен.

Выполнение задачи 1 займет 1 секунду, поэтому она перейдет к следующей строке кода для выполнения.
теперь он собирается выполнить задачу 2, но ей нужен результат, полученный от задачи 1. но он недоступен, потому что это займет 1 секунду. Вот почему вы можете увидеть эту неопределенную ошибку.

Здесь мы подходим к решению обратного вызова.

// Sequential operation, task of one result determine result of other task.
// callback and callback hell
function task1(id, callback) {
  setTimeout(() => {
    const userDetails = { id, userGithubName: "bittu" + id };
    callback(userDetails);
  }, 1000);
}

function task2(userGithubName, callback) {
  setTimeout(() => {
    const repoDetails = { userGithubName, repos: ['repo1', 'repo2', 'repo3'] };
    callback(repoDetails);
  }, 2000);
}

function task3(repoName, callback) {
  setTimeout(() => {
    const commitDetails = { repoName, commits: ['commit1', 'commit2', 'commit3'] };
    callback(commitDetails);
  }, 3000);
}

task1(2, (user) => {
  task2(user.userGithubName, (repos) => {
    task3(repos.repos[0], (commits) => {
      console.log("Result: ", commits.commits);
    });
  });
});


// "Result:", ["commit1", "commit2", "commit3"]

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

Затем выполнится задача 1, затем
выполнится задача 2,
выполнится задача 3

Итак, вы видели пример получения коммитов с обратным вызовом, где каждая задача зависит от предыдущей задачи. и при решении этой проблемы мы столкнулись с вложенным обратным вызовом, который называется ад обратного вызова,

Возникают проблемы с поддержкой и читаемостью кода.

Итак, обещание появляется для решения этой проблемы.

// Solution through promise

function task1(id){
  return new Promise((resolve, reject)=>{
    setTimeout(()=>{
        const userDetails = { id:id, userGithubName: "bittu" + id };
          resolve(userDetails)
    }, 1000)
  })
} 

function task2(githubUserName){
 return new Promise((resolve, reject)=>{
   setTimeout(()=>{
     const repoDetails= {githubUserName:githubUserName, repos:['repo1', 'repo2', 'repo3'] };
      resolve(repoDetails);
    }, 2000)
  })
}

function task3(repoName){
 return new Promise((resolve, reject)=>{
      setTimeout(()=>{
      let commitDetails= {repoName:repoName, commits:['commit1', 'commit2','commit3' ]};
     resolve(commitDetails)
     }, 1000)
  })
}


 task1(2)
   .then((user)=>{
   return task2(user.userGithubName);
   })
   .then((repoDetails)=>{
  return task3(repoDetails.repos[0])
   })
   .then((commits)=>{
  console.log(commits.commits)
})
.catch((error) => {
    console.error("Error:", error);
  });


// ["commit1", "commit2", "commit3"]

Таким образом, мы получаем тот же результат и через обещание. Читабельность становится немного лучше, но опять же, если вы видите, что есть какие-то проблемы с поддержкой кода из-за вложенности then. Это называется цепочкой обещаний.

Это лучше, чем обратный вызов, и его можно использовать.

Но снова мы получаем еще одно решение для большей оптимизации:

Асинхронное ожидание

Это наиболее упрощенный способ решения нашей проблемы, предоставляемый Javascript.

Здесь у нас нет изменений, все будет так же, как и у промиса, но способ обработки последней части, где идет цепочка промисов, — немного простой и читабельный.

// Async-await


 function task1(id){
  return new Promise((resolve, reject)=>{
    setTimeout(()=>{
        const userDetails = { id:id, userGithubName: "bittu" + id };
          resolve(userDetails)
    }, 1000)
  })
} 

function task2(githubUserName){
 return new Promise((resolve, reject)=>{
   setTimeout(()=>{
     const repoDetails= {githubUserName:githubUserName, repos:['repo1', 'repo2', 'repo3'] };
      resolve(repoDetails);
    }, 2000)
  })
}

function task3(repoName){
 return new Promise((resolve, reject)=>{
      setTimeout(()=>{
      let commitDetails= {repoName:repoName, commits:['commit1', 'commit2','commit3' ]};
     resolve(commitDetails)
     }, 1000)
  })
}

async function main(){
 const user= await task1(2);
  const repoDetails= await task2(user.userGithubName);
  const commits= await task3(repoDetails.repos[0]);
  console.log(commits.commits)
}

main();

//["commit1", "commit2", "commit3"] 

Разница есть только в последней части,

Итак, здесь нам нужно создать функцию async, и там, где нам нужна последовательная операция, перед ней нужно поставить ключевое слово await. Единственное условие — там, где мы ставим await, эта функция должна возвращать обещание …….

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

Мы взяли одну задачу и решили ее всеми тремя способами.

Спасибо за прочтение. Приятного обучения.

До скорой встречи в следующей статье.