SolidJS uygulamalarınızda asenkron görevleri yönetmek, iptal edilebilir API çağrıları yapmak ve sunucu hatalarını güvenle tipli bir şekilde ele almak için LazyPromise kullanımını keşfedin. Kullanıcı deneyimini artıran bu modern yaklaşımları adım adım öğrenin.
Günümüzün interaktif web uygulamalarında, kullanıcılar sürekli değişen bir deneyim beklerler. Bir arama kutusuna yazı yazmaya başladığınızda anında sonuçlar görmek, bir dosyayı yüklerken ilerleme çubuğunu izlemek veya bir sayfadan ayrılırken arka plandaki gereksiz veri çekme işlemlerinin durdurulduğunu bilmek, modern web deneyiminin temel taşlarıdır. Peki, arka planda çalışan ve artık ihtiyacımız olmayan bir veri isteği ne olacak? Ya da kullanıcının sayfayı terk etmesiyle anlamsız hale gelen uzun süreli bir işlem? İşte tam bu noktada “iptal edilebilir asenkron işlemler” kavramı devreye giriyor.
İptal edilebilirlik, sadece performans açısından değil, aynı zamanda uygulama kaynaklarının verimli kullanımı ve kullanıcı deneyimi açısından da hayati bir öneme sahiptir. Düşünsenize, bir kullanıcı arama kutusuna “SolidJS” yazdı, sonra fikrini değiştirip “LazyPromise” yazdı. İlk “SolidJS” araması için yapılan API isteği hala sunucuya ulaşıyor ve belki de büyük bir veri setini geri döndürüyor. Bu gereksiz işlem, hem sunucu kaynaklarını tüketir hem de istemcinin ağ bant genişliğini meşgul eder. En önemlisi, sonuçlar geri döndüğünde, kullanıcı zaten “LazyPromise” sonuçlarını beklediği için bu veriler tamamen göz ardı edilir. Bu durum, özellikle mobil cihazlarda veya yavaş internet bağlantılarında daha belirgin bir sorun haline gelir.
Ayrıca, bir kullanıcı bir sayfadan diğerine hızla geçiş yaptığında, önceki sayfada başlatılan ancak henüz tamamlanmamış asenkron işlemlerin durdurulması gerekir. Aksi takdirde, bu işlemler tamamlandığında bile uygulama belleğinde veya UI’da beklenmedik durumlar (memory leaks, race conditions, stale data) yaratabilirler. Bu tür durumlar, uygulamanın kararlılığını bozar ve hata ayıklamayı zorlaştırır. İşte bu yüzden, asenkron görevleri programatik olarak durdurabilme yeteneği, sağlam ve duyarlı bir kullanıcı arayüzü (UI) geliştirmek için vazgeçilmezdir. Bu makalede, SolidJS’in reaktif yapısını LazyPromise’in güçlü iptal mekanizmalarıyla birleştirerek bu sorunları nasıl aşabileceğimizi detaylıca inceleyeceğiz. Ayrıca, asenkron işlemlerin diğer önemli bir yönü olan sunucu hatalarını tipli bir şekilde ele almanın uygulamanızın güvenilirliğini nasıl artıracağını da ele alacağız. Bu yaklaşımlar, modern web geliştirmenin en iyi pratikleri arasında yer almaktadır.
SolidJS, Asenkron Programlama ve Hata Yönetimi Temelleri Nelerdir?
Modern web geliştirmenin temel taşlarından biri, kullanıcı arayüzünü (UI) bloklamadan veri çekme, dosya işleme ve karmaşık hesaplamalar gibi işlemleri gerçekleştirmektir. Bu, asenkron programlamanın temelini oluşturur. SolidJS, bu bağlamda, reaktif ve yüksek performanslı bir UI kütüphanesi olarak öne çıkar. Ancak SolidJS’in kendisi, asenkron işlemleri yönetmek için doğrudan bir mekanizma sunmaz; bunun yerine JavaScript’in yerleşik Promise API’si ve Async/Await sözdizimi ile uyumlu bir şekilde çalışır. Verimli bir SolidJS uygulaması için bu temelleri iyi anlamak kritik öneme sahiptir.
Asenkron programlamada, bir işlem genellikle hemen bir sonuç döndürmez, bunun yerine gelecekte bir zamanda tamamlanacağını garanti eden bir “promise” döndürür. Bu promise, işlemin başarılı olup olmadığını (resolve) veya bir hata ile karşılaşıp karşılaşmadığını (reject) temsil eder. JavaScript’in async/await sözdizimi, bu promise’lerle çalışmayı senkron koda benzer hale getirerek okunabilirliği artırır. Ancak, standart Promise API’sinde doğrudan bir iptal mekanizması bulunmaz. Bir promise bir kez başlatıldığında, ya tamamlanır ya da bir hata ile sonuçlanır; ancak dışarıdan durdurulamaz. Bu eksiklik, yukarıda bahsettiğimiz gibi gereksiz işlemlerin birikmesine neden olabilir.
Hata yönetimi ise asenkron programlamanın ayrılmaz bir parçasıdır. Ağ istekleri, sunucu hataları, yetkilendirme sorunları veya beklenmedik veri formatları gibi birçok nedenle başarısız olabilir. Bu hataları doğru bir şekilde yakalamak ve kullanıcıya anlamlı geri bildirimler sunmak, uygulamanın güvenilirliği ve kullanıcı memnuniyeti için esastır. Geleneksel olarak, JavaScript’te hatalar try...catch blokları veya promise’lerin .catch() metodu ile yönetilir. Ancak bu yaklaşımlar genellikle genel hata mesajları sunar. Özellikle karmaşık kurumsal uygulamalarda, sunucudan dönen hata tiplerini önceden bilmek ve bunlara özel tepkiler vermek (örneğin, “Kullanıcı Adı Zaten Kullanılıyor” yerine genel bir “Hata Oluştu” mesajı vermek) çok daha verimli ve kullanıcı dostudur. TypeScript, bu noktada devreye girerek, sunucu hata yanıtlarını tipli bir şekilde modellememize ve böylece hataları daha güvenli ve öngörülebilir bir şekilde ele almamıza olanak tanır.
Bu bölümün geri kalanında, tam da bu eksiklikleri gidermek için tasarlanmış bir kütüphane olan LazyPromise’in ne olduğunu ve tipli sunucu hatalarının neden gerekli olduğunu derinlemesine inceleyeceğiz. Bu kavramlar, SolidJS’in reaktif doğasıyla birleştiğinde, hem performanslı hem de bakımı kolay uygulamalar geliştirmenize yardımcı olacaktır.
LazyPromise Nedir ve Nasıl Çalışır?
LazyPromise, adından da anlaşılacağı gibi “tembel” bir promise uygulamasıdır. Standart JavaScript Promise’lerinin aksine, bir LazyPromise örneği oluşturulduğunda içerisindeki asenkron işlem hemen başlamaz. Bunun yerine, işlem yalnızca .then() veya .catch() metotları çağrıldığında tetiklenir. Bu “tembel başlatma” özelliği, asenkron görevleri gerektiğinde başlatma esnekliği sağlar ve gereksiz kaynak tüketimini önler. Ancak LazyPromise’in asıl gücü, iptal edilebilir olmasıdır.
LazyPromise, AbortController ve AbortSignal API’lerini kullanarak promise’lerin iptal edilebilirliğini sağlar. Bir AbortController oluşturduğunuzda, bu size bir signal nesnesi ve bir abort() metodu sunar. signal nesnesini, fetch istekleri gibi asenkron işlemlere bir seçenek olarak geçirebilirsiniz. Eğer controller.abort() çağrılırsa, signal nesnesi “aborted” durumuna geçer ve bu sinyali dinleyen tüm işlemler otomatik olarak iptal edilir. Örneğin, bir fetch isteğine signal parametresini verdiğinizde, istek sunucudan yanıt gelmeden iptal edilebilir ve bir AbortError fırlatır.
LazyPromise, bu mekanizmayı kendi bünyesinde barındırır. Bir LazyPromise oluştururken, işlevinize bir signal nesnesi ve bir controller nesnesi geçirilir. Bu sayede, promise içinde uzun süreli bir işlem başlatabilir ve bu işlemin durumunu izleyebilirsiniz. Eğer dışarıdan promise’in .cancel() metodu çağrılırsa, LazyPromise otomatik olarak içerisindeki AbortController‘ı kullanarak ilişkili tüm işlemleri iptal eder. Bu, özellikle kullanıcı arayüzü etkileşimleriyle tetiklenen API çağrılarında veya uzun süreli hesaplamalarda çok kullanışlıdır. Kullanıcı bir işlem devam ederken başka bir eylem başlattığında, önceki işlemin kolayca durdurulmasını sağlar ve böylece yarış koşullarının (race conditions) ve gereksiz kaynak kullanımının önüne geçer.
Özetle, LazyPromise’in temel özellikleri şunlardır:
- Tembel Başlatma: İşlem,
.then()veya.catch()çağrılana kadar başlamaz. - İptal Edilebilirlik:
.cancel()metodu ile devam eden asenkron işlemler durdurulabilir. - AbortController Entegrasyonu: Modern tarayıcı API’leri ile uyumlu bir iptal mekanizması sunar.
- SolidJS ile Uyumlu: Reaktif SolidJS uygulamalarında, özellikle
createResourceveyacreateEffectile kullanıldığında güçlü bir sinerji oluşturur.
Tipli Sunucu Hataları Neden Gerekli?
Web uygulamalarında kullanıcı deneyimini ve geliştirici verimliliğini artıran en önemli faktörlerden biri, hata yönetiminin sağlam ve öngörülebilir olmasıdır. Sunucu tarafında meydana gelen hatalar, ağ kesintileri, doğrulama sorunları, yetkilendirme yetersizlikleri veya sunucu tarafındaki iç hatalar gibi çeşitli nedenlerle ortaya çıkabilir. Bu hataları yakalamak ve kullanıcıya doğru bilgiyi sunmak, uygulamanızın kalitesini doğrudan etkiler. İşte tam bu noktada, “tipli sunucu hataları” kavramı devreye girer.
Geleneksel olarak, sunucu hataları genellikle genel HTTP durum kodları (örneğin, 400 Bad Request, 401 Unauthorized, 404 Not Found, 500 Internal Server Error) ve bazen basit bir JSON objesi içinde bir mesajla ({ "message": "Bir hata oluştu" }) temsil edilir. Ancak bu yaklaşım, istemci tarafında hatanın gerçek nedenini programatik olarak anlamayı ve buna özel bir UI veya iş mantığı uygulamayı zorlaştırır. Örneğin, bir formda “Bu e-posta adresi zaten kullanımda” hatası ile “Şifre çok kısa” hatası arasında ayrım yapmak isterseniz, sadece genel bir hata mesajı yeterli olmayacaktır.
Tipli sunucu hataları, TypeScript’in gücünü kullanarak sunucudan dönen hata yanıtlarını belirli arayüzler (interface) veya tipler (type) aracılığıyla modellememizi sağlar. Bu, istemci tarafında gelen hata nesnesinin yapısını önceden bilmemize olanak tanır. Böylece, gelen hata yanıtını güvenli bir şekilde kontrol edebilir, farklı hata tiplerine göre farklı UI elementleri gösterebilir veya farklı iş akışlarını tetikleyebiliriz. Örneğin, bir ValidationError tipinde bir hata gelirse, bu hatanın içerdiği spesifik alan hatalarını formdaki ilgili input alanlarının altına yerleştirebiliriz. Eğer bir UnauthorizedError gelirse, kullanıcıyı oturum açma sayfasına yönlendirebiliriz.
Tipli sunucu hataları kullanmanın avantajları:
- Geliştirici Deneyimi: Hataların yapısını net bir şekilde bilmek, hata ayıklama ve yönetimini kolaylaştırır. Otomatik tamamlama (autocomplete) ve tip güvenliği sayesinde hataları daha hızlı ve hatasız bir şekilde işleyebilirsiniz.
- Kullanıcı Deneyimi: Kullanıcılara daha spesifik, anlaşılır ve eyleme geçirilebilir hata mesajları sunulmasını sağlar. Bu da hayal kırıklığını azaltır ve uygulamanın kullanılabilirliğini artırır.
- Kod Güvenliği: TypeScript’in tip kontrolü sayesinde, hataları yanlış yorumlama veya eksik işleme olasılığı azalır. Çalışma zamanı hataları yerine derleme zamanı hataları yakalanır.
- Bakım Kolaylığı: Hata işleme mantığı daha modüler ve okunabilir hale gelir, bu da uygulamanın uzun vadeli bakımını kolaylaştırır.
Sonuç olarak, tipli sunucu hataları, modern ve güvenilir web uygulamaları geliştirmenin vazgeçilmez bir parçasıdır. SolidJS ve TypeScript ile bu yaklaşımı benimseyerek, uygulamanızın hata durumlarında bile profesyonel ve duyarlı kalmasını sağlayabiliriz.
SolidJS ve LazyPromise ile İptal Edilebilir İstekler Nasıl Yapılır?
SolidJS’in reaktif yapısı ve LazyPromise’in iptal edilebilir Promise mekanizmasını bir araya getirerek, karmaşık asenkron işlemleri etkili bir şekilde yönetebiliriz. Bu bölüm, SolidJS bileşenlerinizde LazyPromise’i nasıl kullanacağınızı adım adım göstermeye odaklanacaktır. Gerçek dünya senaryolarında sıklıkla karşılaşılan durumlar üzerinden örnekler sunarak, bu kombinasyonun gücünü gözler önüne sereceğiz. Başlangıç olarak, LazyPromise’i projenize dahil etmeniz gerekecektir. Bunun için basit bir npm veya yarn komutu yeterlidir:
npm install lazy-promise
# veya
yarn add lazy-promise
Kurulum tamamlandıktan sonra, LazyPromise'i SolidJS ortamında kullanmaya başlayabiliriz. En yaygın senaryolardan biri, bir kullanıcı arayüzü etkileşimi (örneğin bir düğmeye tıklama veya bir input alanına yazma) sonucunda bir API isteği başlatmaktır. Eğer kullanıcı aynı eylemi çok hızlı bir şekilde tekrarlarsa veya işlemi başlattıktan sonra vazgeçerse, önceki isteklerin iptal edilmesi gerekliliği doğar. İşte bu noktada LazyPromise'in iptal yeteneği devreye girer.
SolidJS'te asenkron veri çekme işlemlerini yönetmek için genellikle createResource veya createEffect kancalarını kullanırız. createResource, özellikle verilerin UI'da gösterileceği ve yükleme/hata durumlarının kolayca yönetileceği durumlar için tasarlanmıştır. LazyPromise ile createResource'ı kullanarak, veri çekme işlemlerini iptal edilebilir hale getirebiliriz. Böylece, bir kaynağın değeri değiştiğinde veya bileşen kaldırıldığında önceki isteklerin iptal edilmesini sağlayabiliriz.
Bu yaklaşım, özellikle hızlı ve duyarlı bir kullanıcı deneyimi sunmak istediğinizde çok değerlidir. Örneğin, bir kullanıcı bir arama terimini hızla değiştirdiğinde, her yeni karakter girişinde yeni bir arama isteği tetiklenir. Önceki isteklerin iptal edilmemesi, sunucuya gereksiz yük bindirir ve istemci tarafında güncel olmayan verilerin gösterilmesine neden olabilecek yarış koşulları yaratır. LazyPromise, bu tür durumları zarifçe çözerek uygulamanızın daha kararlı ve verimli çalışmasını sağlar. Şimdi, LazyPromise'in temel kullanımına ve API isteklerini iptal etme senaryolarına daha yakından bakalım.
Temel Bir LazyPromise Kullanımı
LazyPromise'in temel kullanımını anlamak için basit bir örnekle başlayalım. Bir bileşen içinde bir düğmeye tıklama olayıyla tetiklenen bir veri çekme işlemi düşünelim. Amacımız, kullanıcı düğmeye tekrar tıkladığında önceki isteği iptal etmektir.
import { createSignal, createEffect } from 'solid-js'; import { LazyPromise } from 'lazy-promise'; function FetchComponent() { const [data, setData] = createSignal(null); const [loading, setLoading] = createSignal(false); const [error, setError] = createSignal(null); // LazyPromise'i saklamak için bir sinyal const [currentPromise, setCurrentPromise] = createSignal(null); const fetchData = async () => { // Önceki isteği iptal et const prevPromise = currentPromise(); if (prevPromise) { prevPromise.cancel(); console.log("Önceki istek iptal edildi."); } setLoading(true); setError(null); setData(null); // Yeni bir LazyPromise oluştur const newPromise = new LazyPromise(async (resolve, reject, signal) => { try { console.log("Yeni istek başlatıldı..."); // API çağrısı için fetch kullanırken signal'i iletin const response = await fetch('https://jsonplaceholder.typicode.com/todos/1', { signal }); // Yanıt başarılı değilse hata fırlat if (!response.ok) { throw new Error(HTTP Hata! Durum: ${response.status}); } const result = await response.json(); resolve(result); } catch (err) { // İptal hatasını özel olarak ele alın if (err.name === 'AbortError') { console.log("İstek iptal edildiği için yakalandı:", err.message); // İptal edilen istekler için hata göstermemize gerek yok // setLoading(false) yapılabilir ama genellikle zaten bir sonraki istek devreye girer. return; // İptal edilen istekler için Promise'i reject etmeyin } reject(err); } }); setCurrentPromise(newPromise); newPromise .then(res => { setData(res); setLoading(false); console.log("İstek tamamlandı:", res); }) .catch(err => { // Eğer bir önceki istek iptal edildiyse, bu catch bloğu çalışmayabilir. // Ama diğer hatalar (ağ hatası vb.) burada yakalanır. setError(err.message); setLoading(false); console.error("İstek hatası:", err); }); }; return ({error() &&Hata: {error()}
} {data() && (Veri:
{JSON.stringify(data(), null, 2)})}
);
}export default FetchComponent;
Yukarıdaki örnekte,
fetchDatafonksiyonu her çağrıldığında, öncelikle öncekiLazyPromise(eğer varsa) iptal edilir. Yeni birLazyPromiseoluşturulur vefetchisteğinesignalparametresi olarak geçirilir. Bu,LazyPromise.cancel()çağrıldığında ilgilifetchisteğinin de iptal edilmesini sağlar.AbortError, iptal edilen isteklerde doğal olarak fırlatılır ve bu hata özel olarak ele alınarak UI'da bir hata mesajı gösterilmesi önlenir. Bu, kullanıcının deneyimini kesintisiz hale getirir.API İsteklerini İptal Etme Senaryoları
LazyPromise'i farklı SolidJS kullanım senaryolarına entegre edebiliriz. En yaygın iki senaryo, kullanıcı arayüzü etkileşimleriyle tetiklenen isteklerin ve bileşenin yaşam döngüsüyle ilişkili isteklerin yönetilmesidir.
Arama Kutusu Dinamik Veri Çekme
Bir arama kutusuna yazarken anında sonuçlar getiren otomatik tamamlama (autocomplete) özelliği, iptal edilebilir istekler için mükemmel bir örnektir. Kullanıcı her karakter girdiğinde yeni bir arama isteği tetiklenir, ancak yalnızca en son isteğin sonuçları gösterilmelidir. Önceki istekler ise gereksiz hale gelir.
import { createSignal, createEffect } from 'solid-js'; import { LazyPromise } from 'lazy-promise'; function SearchComponent() { const [query, setQuery] = createSignal(''); const [results, setResults] = createSignal([]); const [loading, setLoading] = createSignal(false); const [error, setError] = createSignal(null); let currentSearchPromise = null; // Aktif LazyPromise'i tutmak için createEffect(() => { const currentQuery = query(); if (currentQuery.length < 2) { // Minimum 2 karakterde ara setResults([]); return; } // Önceki isteği iptal et if (currentSearchPromise) { currentSearchPromise.cancel(); console.log("Önceki arama isteği iptal edildi."); } setLoading(true); setError(null); // Debounce veya throttle kullanarak istek sıklığını kontrol edebilirsiniz // Bu örnekte basitleştirilmiş bir debounce etkisi için setTimeout kullandık. const searchTimeout = setTimeout(() => { const newPromise = new LazyPromise(async (resolve, reject, signal) => { try { console.log("${currentQuery}" için arama başlatıldı.); // Mock API çağrısı, gerçekte bir API'ye istek gönderirdiniz const response = await fetch(https://jsonplaceholder.typicode.com/posts?q=${currentQuery}, { signal } ); if (!response.ok) { throw new Error(HTTP Hata! Durum: ${response.status}); } const data = await response.json(); resolve(data.slice(0, 5)); // İlk 5 sonucu göster } catch (err) { if (err.name === 'AbortError') { console.log("Arama isteği iptal edildi:", err.message); return; // İptal edilen istek için hata gösterme } reject(err); } }); currentSearchPromise = newPromise; // Yeni promise'i sakla newPromise .then(res => { setResults(res); setLoading(false); currentSearchPromise = null; // Başarılı olunca promise'i temizle console.log("${currentQuery}" için sonuçlar geldi.); }) .catch(err => { setError(err.message); setLoading(false); currentSearchPromise = null; // Hata olunca promise'i temizle console.error("${currentQuery}" için arama hatası:, err); }); }, 300); // 300ms'lik debounce süresi // Bileşen kaldırıldığında veya query değiştiğinde timeout'u temizle return () => clearTimeout(searchTimeout); }); return (setQuery(e.currentTarget.value)} /> {loading() &&); } export default SearchComponent;Yükleniyor...
} {error() &&Hata: {error()}
}{results().map((post) => (
- {post.title}
))}Bu örnekte,
createEffectherquerysinyali değiştiğinde çalışır. Yeni bir arama başlamadan öncecurrentSearchPromisekontrol edilir ve eğer varsa iptal edilir. Bu, önceki arama isteklerinin tamamlanmasını engeller ve kaynak israfını önler.setTimeoutkullanımı, basit bir debounce etkisi yaratarak gereksiz API çağrılarını daha da azaltır.Bileşen Mount/Unmount Durumlarında İstek İptali
Bir bileşen DOM'dan kaldırıldığında, o bileşenin başlattığı tüm uzun süreli asenkron işlemlerin de durdurulması iyi bir pratiktir. SolidJS'in
onCleanupkancası ile bu işlemi kolayca yapabiliriz.import { createSignal, onCleanup } from 'solid-js'; import { LazyPromise } from 'lazy-promise'; function LifecycleFetchComponent() { const [data, setData] = createSignal(null); const [loading, setLoading] = createSignal(true); const [error, setError] = createSignal(null); // LazyPromise'i tutacak bir değişken let dataFetchPromise = null; // Bileşen yüklendiğinde veri çekme işlemini başlat // createEffect veya doğrudan component scope'unda çağrılabilir dataFetchPromise = new LazyPromise(async (resolve, reject, signal) => { try { console.log("Bileşen yüklendi, veri çekiliyor..."); // Uzun süren bir işlem simülasyonu const response = await fetch('https://jsonplaceholder.typicode.com/posts/1', { signal }); if (!response.ok) { throw new Error(HTTP Hata! Durum: ${response.status}); } const result = await response.json(); resolve(result); } catch (err) { if (err.name === 'AbortError') { console.log("Veri çekme işlemi iptal edildi (bileşen kaldırıldı)."); return; } reject(err); } }); dataFetchPromise .then(res => { setData(res); setLoading(false); console.log("Veri başarıyla çekildi."); }) .catch(err => { setError(err.message); setLoading(false); console.error("Veri çekme hatası:", err); }); // Bileşen DOM'dan kaldırıldığında isteği iptal et onCleanup(() => { if (dataFetchPromise) { dataFetchPromise.cancel(); console.log("onCleanup: Devam eden veri çekme işlemi iptal edildi."); } }); return (); } export default LifecycleFetchComponent;Bileşen Yaşam Döngüsü ile İptal Edilebilir İstek
{loading() &&Yükleniyor...
} {error() &&Hata: {error()}
} {data() && ()}Gönderi Detayı:
Başlık: {data().title}
İçerik: {data().body}
Bu örnekte,
LifecycleFetchComponentyüklendiğinde bir API isteği başlatılır. Eğer bileşen kullanıcı tarafından DOM'dan kaldırılırsa (örneğin, bir yönlendirme veya koşullu render etme ile),onCleanupkancası devreye girer ve devam edenLazyPromise'i iptal eder. Bu, gereksiz arka plan işlemlerinin çalışmasını durdurarak uygulama kaynaklarının verimli kullanılmasını sağlar ve olası bellek sızıntılarını önler.Uzman İpucu: Karmaşık uygulamalarda, birden fazla iptal edilebilir isteği aynı anda yönetmek için bir merkezi istek yöneticisi (request manager) kullanmayı düşünebilirsiniz. Bu, belirli bir etiketle gruplandırılmış tüm istekleri tek bir çağrıyla iptal etmenize olanak tanır.Tipli Hata Yönetimi: Sunucu Hatalarını TypeScript ile Nasıl Modellemeliyiz?
Uygulamalarımızın sağlamlığını ve bakıma alınabilirliğini artırmanın temel yollarından biri, sunucu hatalarını etkili ve tutarlı bir şekilde yönetmektir. TypeScript, bu süreçte bize güçlü bir araç seti sunarak, sunucudan dönen hata yanıtlarını belirli tiplerle modellememizi sağlar. Bu sayede, istemci tarafında hataları daha güvenli bir şekilde işleyebilir, kullanıcıya daha spesifik geri bildirimler sunabilir ve geliştirme sürecini kolaylaştırabiliriz. Genel hata mesajları yerine, kullanıcıya sorunun ne olduğunu ve nasıl çözebileceğini anlatan açık ve anlamlı mesajlar sunmak, modern web uygulamalarının olmazsa olmazıdır.
Sunucu hatalarını tipli bir şekilde ele almanın ilk adımı, olası hata senaryolarını ve bunların sunucu tarafındaki yanıt yapılarını belirlemektir. Örneğin, bir doğrulama hatası (validation error) ile bir yetkilendirme hatası (authentication error) farklı bilgi setleri içerecektir. Doğrulama hatası genellikle hangi alanların hatalı olduğunu ve hata mesajlarını içerirken, yetkilendirme hatası sadece bir "yetkisiz erişim" mesajı içerebilir ve kullanıcıyı oturum açmaya yönlendirmeyi gerektirebilir. Bu farklılıkları TypeScript tipleriyle açıkça ifade etmek, istemci kodumuzun bu durumlara daha doğru ve güvenli bir şekilde tepki vermesini sağlar.
Bu yaklaşımla, hataları bir
anytipi olarak ele almak yerine, belirli birinterfaceveyatypeile tanımlarız. Böylece, bir hata nesnesine eriştiğimizde, içindeki özelliklerin ne olduğunu ve nasıl kullanılacağını derleme zamanında biliriz. Bu sadece çalışma zamanı hatalarını azaltmakla kalmaz, aynı zamanda IDE'nizde otomatik tamamlama ve IntelliSense desteği sağlayarak geliştirici verimliliğini de artırır. Ayrıca, hata yönetim mantığınızı daha modüler hale getirmenize ve kod tabanınızda tekrar eden hata işleme bloklarını azaltmanıza yardımcı olur.Şimdi, bu prensipleri SolidJS uygulamalarımızda nasıl uygulayacağımıza dair pratik örneklere geçelim. Bir hata tipi nasıl tanımlanır ve bir API isteği sırasında bu tipler nasıl kullanılır, ayrıntılarıyla inceleyeceğiz. Ayrıca, kullanıcı arayüzünde (UI) bu tipli hata mesajlarını nasıl gösterileceğini de ele alacağız, böylece kullanıcılar sorunlarla karşılaştıklarında net ve faydalı bilgiler edinebilirler.
Bir Hata Tipi Tanımlama ve Kullanımı
Öncelikle, sunucumuzdan gelebilecek farklı hata tiplerini tanımlayalım. Genellikle bir ana hata arayüzü ve bu arayüzü genişleten özel hata tipleri oluşturmak iyi bir yaklaşımdır.
// error-types.ts export interface ServerError { message: string; code: string; // Örneğin: 'VALIDATION_FAILED', 'UNAUTHORIZED', 'NOT_FOUND' timestamp: string; } export interface ValidationError extends ServerError { code: 'VALIDATION_FAILED'; errors: { [field: string]: string[]; // Hatalı alanlar ve mesajları }; } export interface UnauthorizedError extends ServerError { code: 'UNAUTHORIZED'; // Bu hata tipi için ek bilgiye ihtiyacımız olmayabilir } // Hata tipini kontrol etmek için bir type guard export function isValidationError(error: any): error is ValidationError { return error && typeof error === 'object' && error.code === 'VALIDATION_FAILED' && 'errors' in error; } export function isUnauthorizedError(error: any): error is UnauthorizedError { return error && typeof error === 'object' && error.code === 'UNAUTHORIZED'; }Şimdi, bu hata tiplerini kullanarak bir API isteği yapalım ve hata durumlarını ele alalım.
// dataService.ts import { LazyPromise } from 'lazy-promise'; import { ServerError, ValidationError, isValidationError } from './error-types'; interface User { id: string; name: string; email: string; } // Mock API Fonksiyonu export function createUser(userData: { name: string; email: string }, signal: AbortSignal): LazyPromise{ return new LazyPromise(async (resolve, reject) => { try { // API çağrısı simülasyonu const response = await fetch('/api/users', { // Bu bir mock endpoint olabilir method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(userData), signal, }); if (!response.ok) { const errorData = await response.json() as ServerError; // Sunucu hatasını tipli olarak yakala reject(errorData); // Hata objesini reject et return; } const newUser = await response.json() as User; resolve(newUser); } catch (err) { if (err.name === 'AbortError') { console.log("Kullanıcı oluşturma isteği iptal edildi."); return; // İptal edilen istek için reject etmeyin } // Diğer ağ veya istemci tarafı hataları için genel bir ServerError oluştur reject({ message: err.message || 'Bilinmeyen bir hata oluştu.', code: 'UNKNOWN_ERROR', timestamp: new Date().toISOString() }); } }); } Yukarıdaki
createUserfonksiyonunda,LazyPromise'in ikinci tipi (Promise'in reject değeri için)ServerErrorolarak belirtilmiştir. Eğer API yanıtı!response.okise, dönen JSON objesini doğrudanServerErrortipine cast ediyoruz. Bu sayede, hatayı yakalayan kod bloğu bu hatanın yapısını TypeScript sayesinde bilecektir.UI'da Hata Mesajlarını Gösterme
SolidJS bileşenimizde bu tipli hataları nasıl yakalayıp kullanıcıya göstereceğimize bakalım:
import { createSignal } from 'solid-js'; import { createUser } from './dataService'; // Yukarıdaki servis fonksiyonu import { ServerError, ValidationError, isValidationError, isUnauthorizedError } from './error-types'; function UserRegistrationForm() { const [name, setName] = createSignal(''); const [email, setEmail] = createSignal(''); const [loading, setLoading] = createSignal(false); const [globalError, setGlobalError] = createSignal(null); const [fieldErrors, setFieldErrors] = createSignal<{[key: string]: string[]}>({}); const [successMessage, setSuccessMessage] = createSignal(''); let currentCreateUserPromise: LazyPromise | null = null; const handleSubmit = async (e: Event) => { e.preventDefault(); if (currentCreateUserPromise) { currentCreateUserPromise.cancel(); } setLoading(true); setGlobalError(null); setFieldErrors({}); setSuccessMessage(''); currentCreateUserPromise = createUser({ name: name(), email: email() }, new AbortController().signal); currentCreateUserPromise .then(user => { setSuccessMessage( Kullanıcı ${user.name} başarıyla oluşturuldu!); setName(''); setEmail(''); setLoading(false); currentCreateUserPromise = null; }) .catch(error => { // Hata objesinin tipini kontrol et if (isValidationError(error)) { // Doğrulama hatası ise alan bazında göster setFieldErrors(error.errors); setGlobalError({ ...error, message: 'Lütfen formdaki hataları düzeltin.' }); } else if (isUnauthorizedError(error)) { // Yetkilendirme hatası ise kullanıcıyı login sayfasına yönlendir veya mesaj göster setGlobalError({ ...error, message: 'Oturumunuz sona ermiş veya yetkiniz yok. Lütfen tekrar giriş yapın.' }); // navigate('/login'); // Örneğin } else { // Diğer tüm sunucu hataları için genel mesaj setGlobalError(error); } setLoading(false); currentCreateUserPromise = null; console.error("Kullanıcı oluşturma hatası:", error); }); }; return ( ); } export default UserRegistrationForm;Bu örnekte,
.catch()bloğundaisValidationErrorveisUnauthorizedErrorgibi type guard'ları kullanarak gelen hatanın tipini güvenli bir şekilde kontrol ediyoruz. Bu sayede:
ValidationErrordurumunda, spesifik alan hatalarınıfieldErrorssinyeline atayarak ilgili input alanlarının altında gösterebiliyoruz.UnauthorizedErrordurumunda, kullanıcıya özel bir mesaj gösterip belki de giriş sayfasına yönlendirme gibi ek bir işlem yapabiliyoruz.- Diğer genel
ServerErrordurumları için ise daha genel bir hata mesajı sunuyoruz.
Bu tipli yaklaşım, hataları tutarlı, okunabilir ve yönetilebilir bir şekilde ele almamızı sağlar. Hata durumlarının önceden tanımlanmış olması, hem geliştirme sürecini hızlandırır hem de uygulamanın daha güvenilir olmasını sağlar.
Gerçek Dünya Senaryosu: Uzun Süreli Veri Çekme İşlemlerinde Performans ve Kullanıcı Deneyimi Nasıl İyileştirilir?
Gerçek dünya uygulamalarında, kullanıcıların yavaş veya takılgan bir arayüzle karşılaşmasını engellemek kritik öneme sahiptir. Özellikle uzun süreli veri çekme işlemleri, kullanıcının sabrını zorlayabilir ve uygulamanın genel performans algısını düşürebilir. İptal edilebilir asenkron görevler ve tipli hata yönetimi, bu tür senaryolarda hem performansı hem de kullanıcı deneyimini önemli ölçüde iyileştiren temel araçlardır. Bu bölümde, iki yaygın gerçek dünya senaryosu üzerinden bu yaklaşımların pratik faydalarını inceleyeceğiz: dinamik bir arama kutusunun performansı ve dosya yükleme işlemlerindeki kullanıcı geri bildirimi ve iptal mekanizmaları.
Uzun süreli işlemlerin yönetimi, sadece teknik bir gereklilik değil, aynı zamanda kullanıcı psikolojisiyle de ilgilidir. Bir işlem başladığında, kullanıcıya geri bildirim vermek (yükleme göstergeleri, ilerleme çubukları) ve işlemi iptal etme olanağı sunmak, kullanıcının kontrol hissini artırır ve bekleyişin stresini azaltır. Eğer bir işlem gereksiz hale gelirse ve otomatik olarak iptal edilmezse, bu durum hem istemci kaynaklarını (CPU, bellek, ağ bant genişliği) hem de sunucu kaynaklarını (işlem gücü, veritabanı sorguları) boşa harcar. Bu, özellikle yüksek trafikli uygulamalarda veya mobil cihazlarda enerji tüketimi açısından bile önemli hale gelebilir.
Tipli hata yönetimi ise, bu işlemler sırasında meydana gelebilecek sorunları kullanıcıya anlaşılır bir dilde aktarmak için kullanılır. Bir dosya yüklenirken "Dosya boyutu çok büyük" veya "Geçersiz dosya tipi" gibi spesifik bir hata mesajı almak, kullanıcı için genel bir "Yükleme hatası" mesajından çok daha değerlidir. Bu, kullanıcının sorunu hızla anlamasına ve doğru adımları atmasına yardımcı olur. Aksi takdirde, kullanıcı ne yapacağını bilemez ve uygulamadan uzaklaşabilir.
Bu senaryolar, SolidJS'in reaktivitesini LazyPromise'in iptal mekanizması ve TypeScript'in tip güvenliği ile birleştirmenin gerçek değerini gösterir. Amacımız, sadece işlevsel uygulamalar oluşturmak değil, aynı zamanda her etkileşimde kullanıcıya sorunsuz ve güven verici bir deneyim sunmaktır. Şimdi, bu kavramları somutlaştırmak için iki örnek senaryoya daha derinlemesine bakalım.
Arama Kutusu Örneği
Bir e-ticaret sitesindeki ürün arama kutusu veya bir doküman yönetim sistemindeki belge arama özelliği, iptal edilebilir isteklerin en bilinen kullanım alanlarından biridir. Kullanıcılar genellikle arama terimlerini hızla değiştirirler ve her tuş vuruşunda bir API isteği göndermek yerine, sadece son ve güncel isteğin sonuçlarının gösterilmesi beklenir.
import { createSignal, createEffect, onCleanup } from 'solid-js';
import { LazyPromise } from 'lazy-promise';
interface Product {
id: string;
name: string;
price: number;
}
function ProductSearch() {
const [searchTerm, setSearchTerm] = createSignal('');
const [searchResults, setSearchResults] = createSignal([]);
const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal(null);
let currentSearchPromise: LazyPromise | null = null;
let debounceTimeout: NodeJS.Timeout | null = null;
createEffect(() => {
const currentTerm = searchTerm();
if (currentTerm.length < 3) {
setSearchResults([]);
setError(null);
if (currentSearchPromise) {
currentSearchPromise.cancel();
currentSearchPromise = null;
}
if (debounceTimeout) {
clearTimeout(debounceTimeout);
}
return;
}
setLoading(true);
setError(null);
// Önceki debounce timeout'u temizle
if (debounceTimeout) {
clearTimeout(debounceTimeout);
}
debounceTimeout = setTimeout(() => {
// Önceki isteği iptal et
if (currentSearchPromise) {
currentSearchPromise.cancel();
console.log(Eski arama "${searchTerm()}" iptal edildi.);
}
const newSearchPromise = new LazyPromise(async (resolve, reject, signal) => {
try {
console.log(Yeni arama başlatıldı: "${currentTerm}");
// API çağrısı simülasyonu
const response = await fetch(https://api.example.com/products?q=${currentTerm}, { signal });
if (!response.ok) {
throw new Error(Ürün arama hatası: ${response.status});
}
const data = await response.json();
resolve(data as Product[]);
} catch (err) {
if (err.name === 'AbortError') {
console.log("Ürün arama isteği iptal edildi.");
return;
}
reject(err.message || "Bilinmeyen bir hata oluştu.");
}
});
currentSearchPromise = newSearchPromise;
newSearchPromise
.then(res => {
setSearchResults(res);
setLoading(false);
currentSearchPromise = null;
})
.catch(err => {
setError(err);
setLoading(false);
currentSearchPromise = null;
});
}, 500); // 500ms debounce
});
onCleanup(() => {
if (currentSearchPromise) {
currentSearchPromise.cancel();
}
if (debounceTimeout) {
clearTimeout(debounceTimeout);
}
});
return (
setSearchTerm(e.currentTarget.value)}
style="width: 100%; padding: 10px; margin-bottom: 15px; border: 1px solid #ccc; border-radius: 4px;"
/>
{loading() && Ürünler aranıyor...
}
{error() && Hata: {error()}
}
{searchResults().length > 0 ? (
{searchResults().map(product => (
-
{product.name} - ${product.price.toFixed(2)}
))}
) : (
searchTerm().length >= 3 && !loading() && !error() && Sonuç bulunamadı.
)}
);
}
export default ProductSearch;
Bu örnekte, her searchTerm değişikliğinde yeni bir LazyPromise başlatılırken, önceki promise iptal edilir. Ayrıca, debounceTimeout ile gereksiz API çağrıları engellenir. Bu sayede:
- Kullanıcı hızlı yazdığında sadece son isteğin sonuçları işlenir.
- Sunucu üzerindeki yük azalır.
- UI gereksiz güncellemelerden kaçınır ve daha duyarlı kalır.
Dosya Yükleme İlerlemesi ve İptali
Büyük dosyaların yüklenmesi, uzun süren bir işlemdir ve kullanıcıya ilerleme durumu hakkında geri bildirim sağlamak ve yüklemeyi iptal etme yeteneği sunmak kullanıcı deneyimini önemli ölçüde artırır.
import { createSignal, onCleanup } from 'solid-js';
import { LazyPromise } from 'lazy-promise';
import { ServerError } from './error-types'; // Tipli hata tanımlamalarınız
function FileUploader() {
const [file, setFile] = createSignal(null);
const [uploadProgress, setUploadProgress] = createSignal(0);
const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal(null);
const [uploadSuccess, setUploadSuccess] = createSignal(false);
let currentUploadPromise: LazyPromise | null = null;
let abortController: AbortController | null = null;
const handleFileChange = (e: Event) => {
const target = e.target as HTMLInputElement;
if (target.files && target.files[0]) {
setFile(target.files[0]);
setUploadProgress(0);
setError(null);
setUploadSuccess(false);
}
};
const startUpload = () => {
if (!file()) return;
// Önceki yüklemeyi iptal et (varsa)
if (currentUploadPromise) {
currentUploadPromise.cancel();
console.log("Önceki dosya yükleme işlemi iptal edildi.");
}
abortController = new AbortController(); // Her yeni yükleme için yeni AbortController
const signal = abortController.signal;
setLoading(true);
setError(null);
setUploadSuccess(false);
currentUploadPromise = new LazyPromise(async (resolve, reject) => {
try {
console.log("${file()?.name}" dosyası yükleniyor...);
const formData = new FormData();
formData.append('file', file() as File);
const xhr = new XMLHttpRequest(); // İlerleme takibi için XMLHttpRequest kullanıyoruz
xhr.open('POST', '/api/upload', true);
xhr.responseType = 'json'; // Sunucudan JSON yanıt bekliyoruz
// İptal sinyalini dinle
signal.addEventListener('abort', () => xhr.abort());
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
const percentCompleted = Math.round((event.loaded * 100) / event.total);
setUploadProgress(percentCompleted);
}
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response.message || 'Dosya başarıyla yüklendi.');
} else {
// Sunucudan gelen tipli hatayı yakala
reject(xhr.response as ServerError);
}
};
xhr.onerror = () => {
// Ağ hatası veya başka bir hata
reject({ message: 'Ağ hatası veya sunucuya ulaşılamıyor.', code: 'NETWORK_ERROR', timestamp: new Date().toISOString() });
};
xhr.send(formData);
} catch (err) {
if (err.name === 'AbortError') {
console.log("Dosya yükleme iptal edildi.");
return;
}
reject({ message: err.message || 'Yükleme sırasında beklenmeyen bir hata oluştu.', code: 'UNKNOWN_UPLOAD_ERROR', timestamp: new Date().toISOString() });
}
});
currentUploadPromise
.then(message => {
setUploadSuccess(true);
setLoading(false);
setUploadProgress(100); // Başarılı olunca %100 yap
console.log(message);
currentUploadPromise = null;
abortController = null;
})
.catch(err => {
setError(err);
setLoading(false);
setUploadProgress(0); // Hata olunca ilerlemeyi sıfırla
console.error("Dosya yükleme hatası:", err);
currentUploadPromise = null;
abortController = null;
});
};
const cancelUpload = () => {
if (abortController) {
abortController.abort();
console.log("Kullanıcı tarafından yükleme iptal edildi.");
setLoading(false);
setUploadProgress(0);
setError({ message: 'Dosya yükleme işlemi kullanıcı tarafından iptal edildi.', code: 'USER_CANCELLED', timestamp: new Date().toISOString() });
currentUploadPromise = null;
abortController = null;
}
};
onCleanup(() => {
if (currentUploadPromise) {
currentUploadPromise.cancel();
}
if (abortController) {
abortController.abort(); // Bileşen kaldırılırsa XHR isteğini de iptal et
}
});
// Mobile uyumlu stil için basit bir örnek
const progressBarWidth = () => ${uploadProgress()}%;
const isMobile = window.matchMedia("(max-width: 768px)").matches;
return (
Dosya Yükleyici
{file() && Seçilen dosya: {file()?.name}
}
{loading() && (
{uploadProgress()}%
)}
{error() && Hata ({error()?.code}): {error()?.message}
}
{uploadSuccess() && Dosya başarıyla yüklendi!
}
{/* Mobile uyumlu HTML örneği: Media Query ile stil değiştirme */}
);
}
export default FileUploader;
Bu karmaşık örnekte, XMLHttpRequest API'si kullanılarak dosya yükleme ilerlemesi takip edilir. LazyPromise, yükleme işlemini iptal etme yeteneği sağlar. Ayrıca:
uploadProgresssinyali ile yükleme ilerlemesi kullanıcıya görsel olarak sunulur.cancelUploadfonksiyonu, devam eden yüklemeyiAbortControlleraracılığıyla durdurur.- Sunucudan dönen hata tipleri (örneğin dosya boyutu, format hataları)
ServerErrorarayüzü ile ele alınır. onCleanupile bileşen kaldırıldığında yükleme işlemi de otomatik olarak iptal edilir.- Basit bir medya sorgusu (media query) örneği ile mobil cihazlarda buton ve inputların tam genişlikte gösterilmesi sağlanır, böylece mobil uyumluluk vurgulanır.
Bu senaryolar, LazyPromise ve tipli hata yönetiminin sadece kod temizliği sağlamakla kalmayıp, aynı zamanda kullanıcıların uygulamalarınızla nasıl etkileşim kurduğunu doğrudan iyileştirdiğini açıkça göstermektedir. Bu, modern web geliştirmenin temel bir parçasıdır.
İleri Düzey İpuçları: Daha Karmaşık Akışlarda LazyPromise ve Hata Yönetimi İçin Püf Noktaları Nelerdir?
LazyPromise ve tipli hata yönetimi ile SolidJS uygulamalarınızın temellerini sağlamlaştırdıktan sonra, daha karmaşık senaryolarla karşılaşmanız kaçınılmazdır. Çoklu bağımlı istekler, küresel hata işleme stratejileri veya özel iptal mekanizmaları gibi ileri düzey konular, uygulamanızın ölçeklenebilirliğini ve bakım kolaylığını doğrudan etkiler. Bu bölümde, bu zorlukların üstesinden gelmek için kullanabileceğiniz bazı ileri düzey ipuçları ve püf noktalarını inceleyeceğiz.
Özellikle büyük ölçekli uygulamalarda, asenkron işlemlerin yönetimi sadece bireysel bileşenlerle sınırlı kalmayabilir. Bazen birden fazla API çağrısının ardışık olarak veya paralel olarak tamamlanması gerekebilir ve bu zincirdeki herhangi bir adımın başarısız olması durumunda veya tüm zincirin iptal edilmesi gerektiğinde nasıl davranılacağını belirlemek önemlidir. Bu tür senaryolarda, Promise zincirlerini yönetmek ve merkezi bir hata işleme stratejisi benimsemek, kod tekrarını azaltır ve uygulamanın genel hata toleransını artırır.
LazyPromise'in esnek yapısı, yalnızca basit API çağrılarını değil, aynı zamanda daha karmaşık arka plan görevlerini (örneğin, büyük veri setlerini işleme, uzun süreli WebSocket bağlantılarını yönetme) de iptal edilebilir hale getirmek için kullanılabilir. AbortSignal'ı sadece fetch API'si ile değil, aynı zamanda diğer tarayıcı API'leri veya kendi özel asenkron işlevlerinizle de entegre etmek mümkündür. Bu, uygulamanıza daha fazla kontrol ve esneklik kazandırır.
Ayrıca, geliştirici deneyimini iyileştirmek için hata tiplerinizi daha da genişletebilir ve sunucunuzdan dönen hata yanıtlarına dayalı olarak özel hata sınıfları oluşturabilirsiniz. Bu sınıflar, hatayı hem tipli olarak tanımlamanıza hem de ilgili işlevselliği (örneğin, hata mesajını formatlama, belirli loglama işlemleri) kapsüllemenize olanak tanır. Böylece, hata işleme mantığınız daha okunabilir, test edilebilir ve yeniden kullanılabilir hale gelir. İşte bu ileri düzey tekniklerin bazılarına daha yakından bir bakış.
Çoklu Bağımlı İsteklerin İptali ve Yönetimi
Bazen bir isteğin sonucu, başka bir isteğin parametresi olarak kullanılır. Bu gibi bağımlı istek zincirlerinde, zincirdeki herhangi bir isteğin iptal edilmesi, sonraki tüm isteklerin de iptal edilmesini gerektirebilir. LazyPromise, bu tür senaryolar için esnek bir çözüm sunar.
import { createSignal } from 'solid-js';
import { LazyPromise } from 'lazy-promise';
import { ServerError } from './error-types'; // Mevcut hata tipleriniz
interface UserProfile {
id: string;
name: string;
posts: Array<{ id: string; title: string }>;
}
function fetchUserProfile(userId: string, signal: AbortSignal): LazyPromise {
return new LazyPromise(async (resolve, reject) => {
try {
// 1. Kullanıcı bilgilerini çek
const userResponse = await fetch(https://api.example.com/users/${userId}, { signal });
if (!userResponse.ok) throw await userResponse.json() as ServerError;
const user = await userResponse.json();
// 2. Kullanıcının gönderilerini çek (ilk isteğe bağımlı)
const postsResponse = await fetch(https://api.example.com/users/${userId}/posts, { signal });
if (!postsResponse.ok) throw await postsResponse.json() as ServerError;
const posts = await postsResponse.json();
resolve({ ...user, posts });
} catch (err) {
if (err.name === 'AbortError') {
console.log("Kullanıcı profili ve gönderileri çekme işlemi iptal edildi.");
return;
}
reject(err);
}
});
}
function ProfileComponent() {
const [profile, setProfile] = createSignal(null);
const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal(null);
const [userId, setUserId] = createSignal('123'); // Örnek kullanıcı ID
let profileFetchPromise: LazyPromise | null = null;
const loadProfile = () => {
if (profileFetchPromise) {
profileFetchPromise.cancel();
}
setLoading(true);
setError(null);
setProfile(null);
profileFetchPromise = fetchUserProfile(userId(), new AbortController().signal);
profileFetchPromise
.then(data => {
setProfile(data);
setLoading(false);
profileFetchPromise = null;
})
.catch(err => {
setError(err);
setLoading(false);
profileFetchPromise = null;
console.error("Profil yükleme hatası:", err);
});
};
return (
setUserId(e.currentTarget.value)} />
{loading() && Profil yükleniyor...
}
{error() && Hata ({error()?.code}): {error()?.message}
}
{profile() && (
{profile()?.name} Profili
ID: {profile()?.id}
Gönderiler:
{profile()?.posts.map(post => (
- {post.title}
))}
)}
);
}
export default ProfileComponent;
Bu örnekte, fetchUserProfile fonksiyonu iki ardışık fetch isteği yapar ve her ikisine de aynı signal nesnesini geçirir. Eğer profileFetchPromise.cancel() çağrılırsa, her iki istek de iptal edilecek ve kaynak israfı önlenecektir. Bu, karmaşık veri bağımlılıklarını yönetmek için güçlü bir model sunar.
Küresel Hata İşleme ve Hata Sınıfları
Uygulamanız büyüdükçe, hataları merkezi bir yerde ele almak ve belirli hata tiplerine göre farklı tepkiler vermek isteyebilirsiniz. Özel hata sınıfları oluşturmak, bu konuda size yardımcı olabilir.
// custom-errors.ts
interface ErrorDetails {
message: string;
code: string;
timestamp: string;
details?: any; // Ek detaylar için
}
// Temel custom hata sınıfımız
export class CustomAppError extends Error {
public details: ErrorDetails;
constructor(details: ErrorDetails, message?: string) {
super(message || details.message);
this.name = 'CustomAppError';
this.details = details;
// TypeScript'te prototype chain'i doğru ayarlamak için
Object.setPrototypeOf(this, CustomAppError.prototype);
}
}
// Özel bir doğrulama hatası sınıfı
export class CustomValidationError extends CustomAppError {
public validationErrors: { [field: string]: string[] };
constructor(details: ErrorDetails & { errors: { [field: string]: string[] } }) {
super(details, 'Doğrulama hatası oluştu.');
this.name = 'CustomValidationError';
this.validationErrors = details.errors;
Object.setPrototypeOf(this, CustomValidationError.prototype);
}
}
// Özel bir yetkilendirme hatası sınıfı
export class CustomUnauthorizedError extends CustomAppError {
constructor(details: ErrorDetails) {
super(details, 'Yetkisiz erişim.');
this.name = 'CustomUnauthorizedError';
Object.setPrototypeOf(this, CustomUnauthorizedError.prototype);
}
}
// API yanıtını CustomAppError'a dönüştüren bir yardımcı fonksiyon
export async function handleApiResponse(response: Response): Promise {
if (!response.ok) {
const errorData = await response.json();
const commonErrorDetails: ErrorDetails = {
message: errorData.message || 'Bilinmeyen sunucu hatası.',
code: errorData.code || 'HTTP_ERROR',
timestamp: new Date().toISOString(),
details: errorData // Tüm yanıtı detaylara ekle
};
switch (response.status) {
case 400: // Bad Request - Genellikle doğrulama hataları
if (errorData.code === 'VALIDATION_FAILED' && errorData.errors) {
throw new CustomValidationError({ ...commonErrorDetails, errors: errorData.errors });
}
throw new CustomAppError(commonErrorDetails);
case 401: // Unauthorized
throw new CustomUnauthorizedError(commonErrorDetails);
case 404: // Not Found
throw new CustomAppError({ ...commonErrorDetails, code: 'NOT_FOUND', message: 'İstenen kaynak bulunamadı.' });
default:
throw new CustomAppError(commonErrorDetails);
}
}
return response.json() as Promise;
}
Bu yapı sayesinde, API servis fonksiyonlarınızda handleApiResponse fonksiyonunu kullanarak otomatik olarak tipli hata sınıflarını fırlatabilir ve UI bileşenlerinizde bu özel hata sınıflarını instanceof ile kontrol edebilirsiniz. Bu, hata işleme mantığınızı çok daha temiz ve merkezi hale getirir.
// updatedDataService.ts (önceki createUser fonksiyonu yerine)
import { LazyPromise } from 'lazy-promise';
import { CustomAppError, handleApiResponse } from './custom-errors';
interface User { /* ... */ }
export function createNewUser(userData: { name: string; email: string }, signal: AbortSignal): LazyPromise {
return new LazyPromise(async (resolve, reject) => {
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData),
signal,
});
const newUser = await handleApiResponse(response); // Hata işleme burada yapılır
resolve(newUser);
} catch (err) {
if (err instanceof CustomAppError) {
reject(err); // Özel hata sınıfını reject et
} else if (err.name === 'AbortError') {
console.log("Kullanıcı oluşturma isteği iptal edildi.");
return;
} else {
// Beklenmeyen diğer hatalar
reject(new CustomAppError({ message: err.message || 'Bilinmeyen bir hata oluştu.', code: 'CLIENT_ERROR', timestamp: new Date().toISOString(), details: err }));
}
}
});
}
Ve UI'da:
// updatedUserRegistrationForm.tsx
import { createSignal } from 'solid-js';
import { createNewUser } from './updatedDataService';
import { CustomAppError, CustomValidationError, CustomUnauthorizedError } from './custom-errors';
function UpdatedUserRegistrationForm() {
// ... (Sinyaller ve diğer state'ler önceki örnekteki gibi)
const [globalError, setGlobalError] = createSignal(null);
const handleSubmit = async (e: Event) => {
// ... (iptal mekanizması ve loading state'leri önceki örnekteki gibi)
try {
const user = await currentCreateUserPromise.then(res => res); // LazyPromise'i await ile kullanmak
setSuccessMessage(Kullanıcı ${user.name} başarıyla oluşturuldu!);
// ...
} catch (error) {
if (error instanceof CustomValidationError) {
setFieldErrors(error.validationErrors);
setGlobalError(error);
} else if (error instanceof CustomUnauthorizedError) {
setGlobalError(error);
// navigate('/login');
} else if (error instanceof CustomAppError) {
setGlobalError(error);
} else {
// LazyPromise'in AbortError'u veya diğer beklenmeyen JS hataları
setGlobalError(new CustomAppError({ message: error.message || 'Beklenmedik bir istemci hatası oluştu.', code: 'UNEXPECTED_CLIENT_ERROR', timestamp: new Date().toISOString() }));
}
setLoading(false);
currentCreateUserPromise = null;
}
};
// ... (render metodu önceki örnekteki gibi, sadece hata gösterimi farklı)
}
Bu yaklaşımlar, uygulamanızın daha büyük ve karmaşık hale geldiğinde bile yönetilebilir, hata toleranslı ve kullanıcı dostu kalmasını sağlar. LazyPromise'in iptal mekanizması ve TypeScript'in tipli hata sınıflarıyla, modern SolidJS uygulamaları için güçlü bir temel oluşturmuş olursunuz.
Sonuç ve Sıkça Sorulan Sorular: İptal Edilebilirlik ve Tipli Hata Yönetimi Geleceği Nasıldır?
Bu makale boyunca, SolidJS uygulamalarında iptal edilebilir asenkron görevlerin ve tipli sunucu hatalarının önemini, LazyPromise kütüphanesini kullanarak bu kavramları nasıl uygulayacağımızı ve gerçek dünya senaryolarında sağladıkları faydaları detaylı bir şekilde inceledik. Modern web geliştirme, kullanıcı deneyimini merkeze alan ve kaynakları verimli kullanan yaklaşımları benimsemeyi gerektirir. LazyPromise gibi araçlar, JavaScript'in temel Promise yapısına iptal edilebilirliği ekleyerek bu vizyonu gerçeğe dönüştürmemize yardımcı olur. Aynı şekilde, TypeScript ile sunucu hatalarını tipli bir şekilde modellemek, uygulamanın sağlamlığını artırır, geliştirici deneyimini iyileştirir ve kullanıcılara daha anlamlı geri bildirimler sunar.
İptal edilebilir asenkron görevler, özellikle arama, filtreleme, dosya yükleme ve sayfalar arası hızlı geçiş gibi dinamik kullanıcı etkileşimlerinin olduğu alanlarda uygulamanızın duyarlılığını ve performansını önemli ölçüde artırır. Gereksiz ağ isteklerini kesmek, sunucu yükünü azaltmak ve istemci tarafında yarış koşularının önüne geçmek, bu yaklaşımın temel faydaları arasındadır. LazyPromise, standart AbortController API'si ile entegre olarak bu yeteneği sağlam bir şekilde sunar.
Tipli sunucu hataları ise, hata yönetimini bir try...catch bloğunun ötesine taşıyarak, hata nesnesinin yapısını ve anlamını derleme zamanında bilebilmemizi sağlar. Bu sayede, farklı hata türlerine (doğrulama hataları, yetkilendirme hataları vb.) özel olarak tepki verebilir, kullanıcıya spesifik ve eyleme geçirilebilir mesajlar sunabiliriz. TypeScript'in tip güvenliği ve özel hata sınıfları oluşturma yeteneği, bu sürecin güvenli ve sürdürülebilir olmasını sağlar. Bu iki yaklaşım bir araya geldiğinde, SolidJS ile geliştirilen uygulamaların hem performans hem de hata toleransı açısından çok daha üstün olmasını sağlar.
Gelecekte, web platformunun ve JavaScript ekosisteminin gelişimiyle birlikte, asenkron akışların yönetimi ve hata işleme mekanizmaları daha da olgunlaşacaktır. Web Workers ile istemci tarafında daha yoğun hesaplamaların iptal edilebilir bir şekilde yönetilmesi veya daha gelişmiş sunucu-istemci hata anlaşmaları gibi yenilikler görmemiz muhtemeldir. Ancak LazyPromise ve tipli hata yönetimi gibi mevcut araçlar, bu geleceğe hazırlanmak için sağlam bir temel sunmaktadır. Bu makalenin, SolidJS projelerinizde daha performanslı, kararlı ve kullanıcı dostu uygulamalar geliştirmeniz için size ilham verdiğini umuyoruz.
Sıkça Sorulan Sorular
-
LazyPromise ve standart JavaScript Promise arasındaki temel fark nedir?
Temel fark, LazyPromise'in "tembel" olması ve iptal edilebilir olmasıdır. Standart bir Promise, oluşturulduğu anda asenkron işlemi başlatır ve dışarıdan iptal edilemez. LazyPromise ise,
.then()veya.catch()çağrılana kadar işlemi başlatmaz ve.cancel()metodu aracılığıyla aktif bir işlemi durdurabilir. -
Neden
AbortControllerdoğrudan kullanmak yerine LazyPromise kullanmalıyım?AbortController, iptal mekanizmasının temelini sağlar ancak her asenkron işlem için manuel olarak bir kontrolör oluşturmak, sinyali iletmek ve hataları yakalamak geliştiriciye ek yük bindirir. LazyPromise,AbortController'ı kendi içinde kapsülleyerek bu süreci otomatikleştirir ve Promise tabanlı bir arayüzle daha temiz bir iptal mekanizması sunar. Özellikle SolidJS'in reaktif bağlamında, Promise yaşam döngüsünü daha kolay yönetmenizi sağlar. -
Tipli sunucu hataları kullanmak performansı etkiler mi?
Hayır, tipli sunucu hataları kullanmak uygulamanızın çalışma zamanı performansını olumsuz etkilemez. TypeScript, derleme zamanında tip kontrolleri yapar ve derlenmiş JavaScript kodunda ek bir çalışma zamanı yükü oluşturmaz. Aksine, tipli hatalar hata ayıklama süresini kısaltarak geliştirme verimliliğini artırır ve çalışma zamanında oluşabilecek beklenmedik hata durumlarını (örneğin, yanlış özellik erişimi) önleyerek uygulamanın daha kararlı çalışmasını sağlar.
-
SolidJS'in
createResourcekancası ile LazyPromise'i nasıl entegre edebilirim?createResource, bir fetcher fonksiyonu bekler. Bu fetcher fonksiyonu bir Promise döndürmelidir. LazyPromise de bir Promise döndürdüğü için doğrudan kullanılabilir. AncakcreateResource, dahili olarak kendi bir iptal mekanizması sunmadığından, LazyPromise'in iptal yeteneğini kullanmak içincreateResource'un kaynağını tetikleyen sinyal değiştiğinde veya bileşen kaldırıldığında LazyPromise'i manuel olarak iptal etmeniz gerekebilir. Bu, genelliklecreateEffectveonCleanupile yönetilir, yukarıdaki arama kutusu örneğinde olduğu gibi. -
Tüm API isteklerimi LazyPromise ile mi sarmalıyım?
Tüm API isteklerini LazyPromise ile sarmak zorunda değilsiniz. Ancak, kullanıcının hızlı etkileşimde bulunabileceği (arama, filtreleme, dosya yükleme gibi) veya uzun sürebilecek ve bileşen ömrüyle ilişkili olan (sayfadan ayrılınca iptal edilmesi gereken) tüm asenkron işlemler için LazyPromise kullanmak, uygulama duyarlılığını ve kaynak verimliliğini önemli ölçüde artıracaktır. İptal edilebilirliğin bir fayda sağlamadığı basit, tek seferlik istekler için standart Promise'leri kullanmaya devam edebilirsiniz.