03 Kasım 2022 • 35 dakikalık okuma
Javascript Callback Hell, bir web sayfasında birden fazla ve ardışık şekilde asenkron ajax sorgusu yapıldığı durumda ortaya çıkan sorundur.
Asynchronous JavaScript and XML, (kısaltması: Ajax) web sayfasının tamamını değil, sadece ilgili bölümünün sunucuya gönderilip işlem yapılmasını sağlayan yöntemdir. Klasik web sayfalarında bir işlem yapılması için sayfanın tamamı sunucuya gönderilir ve tamamı sunucudan tekrar istemciye geri gönderilir ve sayfa yeniden yüklenir. Ajax yöntemi ile istemci tarafından sadece yapılması istenen işlem arkaplanda (asenkron) sunucuya gönderilir ve sunucudan gelen yanıt sayfanın tamamı yüklenmeden işlem yapılır. Örneğin video izlerken videonun beğenilmesi, yorum yapılması.
Ajax istekleri asenkron çalışırlar. Bir web tarayıcısı web sayfasındaki Javascript kodları yukarıdan aşağıya satır satır okur ve okuduğu satırdaki kodu çalıştırır. Asenkron olmayan yöntemde okunan satırdaki işlem bitmeden bir sonraki kod satıra geçilmez fakat bu yöntemde çalıştırılan kodun işlenmesi uzun sürer ise -örneğin büyük boyutlu bir dosyanın sunucuya yüklenmesi- işlem bitip cevap gelene kadar beklenir. Bir sonraki kod çalıştırılmaz. Yanıt beklenmeye devam ettiği için uygulama alt satırlarda kalan kodları çalıştıramaz, diğer isteklere cevap veremez ve başka işlem yapamaz. Kullanıcı bu durumda başka işlem yapmaya çalışır ise uygulama kilitlenip cevap veremez duruma düşebilir ve uygulama kötü bir kullanıcı deneyimi yaşatır. Bu durumun yaşanmaması için asenkron yöntem ile işlem yapılması gerekir. Asenkron yöntemde uzun süren işlemlerde cevap gelmesi beklenmez ve tarayıcı bir alt satıra geçip sırasıyla sonraki satırlardaki işlemleri yapar. Bu durumda uygulama kullanıcıdan gelen diğer işlemleri yapabilir ve kilitlenmez. Asenkron işlemlerde ise uzun süren işlemin bitmesi sonrasında yapılması istenen işlemler var ise bu işlemlerin yapılması için callback metodu kullanılır.
Callback metodu, asenkron işlem bittikten sonra çalışır. Örneğin; "Dosya yüklenmesi tamamlandı" mesajı kullanıcıya gösterilir ya da dosya bilgileri veritabanına yazdırılır.
Örneğin; asenkron olarak ardışık çalışması gereken beş farklı işlem bulunuyorsa, önceki işlemin tamamlanması gerekiyor veya sonraki işlemler önceki işlemlerden dönen verilere ihtiyaç duyuyor ise, beş farklı callback metodu yazılması ve bu callback metotlarının sırayla birbiri içerisinden çalıştırılması gerekmektedir. Yani asenkron işlem sayısı kadar callback metodu yazılmalıdır. Ayrıca işlem sırasında hata meydana gelmesi durumunda yine hata yönetimi için ilgili metotlar yazılmalıdır. Çok fazla callback metodu yazmak ve hata yönetimi yapmak yazılımcı açısından zor bir işlemdir. Bu duruma callback hell denir.
Asenkron işlem tamamlanması sonrasında sırasıyla başla işlem yapmak için ikinci işlem, birinci işlemin callback metodu içinden çağırılmalıdır. Bu yöntem ile çok sayıda callback metot yazılmak zorunda ve kod tekrarına neden olmaktadır; kodun okunması, anlaşılması ve sonrasında bakımını zorlaştırmaktadır.
Örneğin; aşağıdaki kodda bir veri kaynağından içerikler getiriliyor, işlem sonucunda gelen verilerden kullanıcı kimlik bilgisi alınıp içerik yazarının bilgileri getiriliyor ve sonrasında içerik yorumları getiriliyor;
<!DOCTYPE html>
<html lang="tr">
<head><title>Callback Hell</title></head>
<body>
<div class="post"></div>
<div class="author"></div>
<div class="comments"></div>
<div class="error"></div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
<script type="text/javascript">
// Get Post 1 data
$.ajax({
type: 'GET',
url: 'https://jsonplaceholder.typicode.com/posts/1',
contentType: 'application/json',
dataType: 'json',
success: function(responseDataPost){ // callback success 1 Post.
console.log('Post İstek gönderildi. Gelen yanıt: ', responseDataPost);
$('.post').append("<h1>Post</h1><h2>" + responseDataPost.title + "</h2><p>" + responseDataPost.body + "</p>");
// Get Post 1 author
$.ajax({
type: 'GET',
url: 'https://jsonplaceholder.typicode.com/users/' + responseDataPost.userId,
contentType: 'application/json',
dataType: 'json',
success: function(responseDataAuthor){ // callback success 2 Author.
console.log('Author İstek gönderildi. Gelen yanıt: ', responseDataAuthor);
$('.author').append("<h1>Author</h1><h3>" + responseDataAuthor.name + "</h3>");
$('.author').append("<p>" + responseDataAuthor.email + "</p>");
$('.author').append("<p>" + responseDataAuthor.website + "</p>");
// Get Post 1 comments
$.ajax({
type: 'GET',
url: 'https://jsonplaceholder.typicode.com/posts/1/comments',
contentType: 'application/json',
dataType: 'json',
success: function(responseDataComments){ // callback success 3 Comments.
console.log('Comments İstek gönderildi. Gelen yanıt: ', responseDataComments);
$('.comments').append("<h1>Comments</h1>");
$.each(responseDataComments, function (index, item){
$('.comments').append("<h5>" + item.email + "</h5><p>" + item.body + "</p>");
});
},
error: function(errorComments){ // callback error 3 Comments.
console.log('Comments Hata oluştu: ', errorComments);
$('.error').append("<h1>" + errorComments.status + " " + errorComments.statusText + "</h1>");
}
});
},
error: function(errorAuthor){ // callback error 2 Author.
console.log('Author Hata oluştu: ', errorAuthor);
$('.error').append("<h1>" + errorAuthor.status + " " + errorAuthor.statusText + "</h1>")
}
});
},
error: function(errorPost){ // callback error 1 Post.
console.log('Post Hata oluştu: ', errorPost);
$('.error').append("<h1>" + errorPost.status + " " + errorPost.statusText + "</h1>");
}
});
</script>
</body>
</html>
Asenkron Ajax isteklerinde callback karmaşasından kaçınmak, kod tekrarı yapmamak için ve callback metotları kullanılabilir;
<!DOCTYPE html>
<html lang="tr">
<head><title>Callback Hell</title></head>
<body>
<div class="post"></div>
<div class="author"></div>
<div class="comments"></div>
<div class="error"></div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
<script type="text/javascript">
// Get Post 1 data
$.ajax({
type: 'GET',
url: 'https://jsonplaceholder.typicode.com/posts/1',
contentType: 'application/json',
dataType: 'json',
success: getAuthor,
error: bindError
});
function getAuthor(responseDataPost){ // callback success 2 Author.
console.log('Post İstek gönderildi. Gelen yanıt: ', responseDataPost);
$('.post').append("<h1>Post</h1><h2>" + responseDataPost.title + "</h2><p>" + responseDataPost.body + "</p>");
// Get Post 1 author
$.ajax({
type: 'GET',
url: 'https://jsonplaceholder.typicode.com/users/' + responseDataPost.userId,
contentType: 'application/json',
dataType: 'json',
success: getComments,
error: bindError
});
}
function getComments(responseDataAuthor){ // callback success 1 Comments.
console.log('Author İstek gönderildi. Gelen yanıt: ', responseDataAuthor);
$('.author').append("<h1>Author</h1><h3>" + responseDataAuthor.name + "</h3>");
$('.author').append("<p>" + responseDataAuthor.email + "</p>");
$('.author').append("<p>" + responseDataAuthor.website + "</p>");
// Get Post 1 comments
$.ajax({
type: 'GET',
url: 'https://jsonplaceholder.typicode.com/posts/1/comments',
contentType: 'application/json',
dataType: 'json',
success: bindComments,
error: bindError
});
}
function bindComments(responseDataComments){ // callback success 3 Comments.
console.log('Comments İstek gönderildi. Gelen yanıt: ', responseDataComments);
$('.comments').append("<h1>Comments</h1>");
$.each(responseDataComments, function (index, item){
$('.comments').append("<h5>" + item.email + "</h5><p>" + item.body + "</p>");
});
}
function bindError(resultError){ // callback error
console.log('Hata oluştu: ', resultError);
$('.error').append("<h1>" + resultError.status + " " + resultError.statusText + "</h1>");
}
</script>
</body>
</html>
Callback metot kullanmak karmaşıklığı azaltsa da metotlar birbiri içinden çağrılmak zorundadır.
Callback metotlar asenkron işlem tamamlandığında çağırılan metotlardır. Promise ise asenkron işlemin durumunu tutan nesnedir ve işlem tamamlandığında işlem sonucunu geri döndürür. Callback metotlara benzer şekilde kullanılır en önemli özelliği promise nesneleri .then() metodu ile arka arkaya çağırılabilirler. Callback metotlara göre daha sade bir kod yazımı sağlarlar.
jQuery kütüphanesindeki $.ajax(), $.get(), $.post() metotları geriye promise nesnesi döndürür. Bu nesne bir değişkene atanarak da kullanılabilir;
<!DOCTYPE html>
<html lang="tr">
<head><title>Callback Hell</title></head>
<body>
<div class="post"></div>
<div class="author"></div>
<div class="comments"></div>
<div class="error"></div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
<script type="text/javascript">
// Get Post 1 data
$.get('https://jsonplaceholder.typicode.com/posts/1').then(function(responseDataPost){
console.log('Post İstek gönderildi. Gelen yanıt: ', responseDataPost);
$('.post').append("<h1>Post</h1><h2>" + responseDataPost.title + "</h2><p>" + responseDataPost.body + "</p>");
return $.get('https://jsonplaceholder.typicode.com/users/' + responseDataPost.userId);
})
.then(function (responseDataAuthor){ // response 2 Author.
console.log('Author İstek gönderildi. Gelen yanıt: ', responseDataAuthor);
$('.author').append("<h1>Author</h1><h3>" + responseDataAuthor.name + "</h3>");
$('.author').append("<p>" + responseDataAuthor.email + "</p>");
$('.author').append("<p>" + responseDataAuthor.website + "</p>");
return $.get('https://jsonplaceholder.typicode.com/posts/1/comments');
})
.then(function(responseDataComments){ // response 3 Comments.
console.log('Comments İstek gönderildi. Gelen yanıt: ', responseDataComments);
$('.comments').append("<h1>Comments</h1>");
$.each(responseDataComments, function (index, item){
$('.comments').append("<h5>" + item.email + "</h5><p>" + item.body + "</p>");
});
},
bindError);
function bindError(resultError){ // callback error
console.log('Hata oluştu: ', resultError);
$('.error').append("<h1>" + resultError.status + " " + resultError.statusText + "</h1>");
}
</script>
</body>
</html>
ECMAScript 2017 spesifikasyonu ile JavaScript, Async/Await söz dizimi ile asenkron işlemler yapabilme yeteneği kazanmıştır. Async ve await komutlarını kullanarak callback ve promise yöntemlerinden çok daha sade, okunaklı ve kolay biçimde asenkron işlemler gerçekleştirilebilmektedir.
<!DOCTYPE html>
<html lang="tr">
<head><title>Callback Hell</title></head>
<body>
<div class="post"></div>
<div class="author"></div>
<div class="comments"></div>
<div class="error"></div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
<script type="text/javascript">
async function getData() {
var responseDataPost = await $.get('https://jsonplaceholder.typicode.com/posts/1'); // Get Post 1 data
console.log('Post İstek gönderildi. Gelen yanıt: ', responseDataPost);
$('.post').append("<h1>Post</h1><h2>" + responseDataPost.title + "</h2><p>" + responseDataPost.body + "</p>");
var responseDataAuthor = await $.get('https://jsonplaceholder.typicode.com/users/' + responseDataPost.userId); // Get Post 1 author
console.log('Author İstek gönderildi. Gelen yanıt: ', responseDataAuthor);
$('.author').append("<h1>Author</h1><h3>" + responseDataAuthor.name + "</h3>");
$('.author').append("<p>" + responseDataAuthor.email + "</p>");
$('.author').append("<p>" + responseDataAuthor.website + "</p>");
var responseDataComments = await $.get('https://jsonplaceholder.typicode.com/posts/1/comments'); // Get Post 1 comments
console.log("responseDataComments", responseDataComments);
$('.comments').append("<h1>Comments</h1>");
$.each(responseDataComments, function (index, item){
$('.comments').append("<h5>" + item.email + "</h5><p>" + item.body + "</p>");
});
}
getData();
</script>
</body>
</html>