Обратные вызовы часто используются, когда у вас есть задачи, выполнение которых требует времени или является асинхронным по своей природе.
Два важных аспекта использования обратного вызова в 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, эта функция должна возвращать обещание …….
Теперь у вас есть сравнительный пример обратного вызова, обещания и асинхронного ожидания.
Мы взяли одну задачу и решили ее всеми тремя способами.
Спасибо за прочтение. Приятного обучения.
До скорой встречи в следующей статье.