CodeGym /Kurslar /JAVA 25 SELF /Asinxron kodda xətaların emalı: exceptionally, handle

Asinxron kodda xətaların emalı: exceptionally, handle

JAVA 25 SELF
Səviyyə , Dərs
Mövcuddur

1. Problem: asinxron kodda istisnalar

Adi (sinxron) kodda hər şey sadədir: metodda istisna baş verərsə, o, çağırış steki üzrə “yuxarı” qalxır və biz onu try-catch vasitəsilə tuta bilərik. Məsələn:

try {
    int x = 1 / 0;
} catch (ArithmeticException ex) {
    System.out.println("Sıfıra bölmə!");
}

Asinxron kodda vəziyyət daha mürəkkəbdir. CompletableFuture.supplyAsync vasitəsilə tapşırığı işə salanda, o, başqa bir axında icra olunur. Əgər orada istisna baş verərsə, o, əsas axına atılmayacaq! Bunun əvəzinə o, CompletableFuture obyektinin içinə “qablaşdırılacaq” və əgər siz sonradan get() və ya join() çağırarsınızsa, bu istisnanı ExecutionException şəklində əldə edəcəksiniz.

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    // Burada xəta!
    return 1 / 0;
});

try {
    Integer result = future.get(); // burada istisna atılacaq!
} catch (Exception ex) {
    System.out.println("Xəta baş verdi: " + ex.getMessage());
}

Amma əgər siz get() çağırmırsınız (bu, özü-özlüyündə o qədər də asinxron deyil), əksinə thenApply və digər metodlarla zəncirlər qurursunuzsa, xəta “itirilə” bilər. Buna görə asinxron proqramlaşdırmada məhz CompletableFuture zəncirlərində xətaları tutmağı və emal etməyi bacarmaq çox vacibdir.

2. exceptionally metodu: xətaları emal etmək və dəyər qaytarmaq

exceptionally metodu sizə, əgər zəncirin əvvəlki mərhələlərində istisna yaranıbsa, onu tutmağa, emal etməyə və alternativ dəyər qaytarmağa imkan verir. Bu, asinxron məlumat axını üçün catch kimidir.

Sxem:

CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)

Nümunə

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    System.out.println("Təhlükəli hesablamanı yerinə yetiririk...");
    if (Math.random() > 0.5) {
        throw new RuntimeException("Nəsə qaydasında deyil!");
    }
    return 42;
});

future = future.exceptionally(ex -> {
    System.out.println("Xəta baş verdi: " + ex.getMessage());
    return 0; // "təhlükəsiz" dəyər qaytarırıq
});

thenAccept ilə nümunə

future.thenAccept(result -> System.out.println("Nəticə: " + result));

Çıxış (təxmini):

Təhlükəli hesablamanı yerinə yetiririk...
Xəta baş verdi: Nəsə qaydasında deyil!
Nəticə: 0
Təhlükəli hesablamanı yerinə yetiririk...
Nəticə: 42

Vacib! exceptionally metodu yalnız zəncirdə ondan əvvəl işlənməmiş istisna yaranıbsa işə düşür. Əgər hər şey qaydasındadırsa, o, nəticəni sadəcə “irəli” buraxır.

3. handle metodu: nəticə və xətanın universal işlənməsi

Bəzən bizə həm nəticəni, həm də xətanı eyni anda emal etmək lazımdır. Məsələn, hər şey yaxşıdırsa — nəticəni qaytarmaq, xəta varsa — ehtiyat variantı qaytarmaq və ya xətanı jurnala yazmaq.

Sxem:

CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn)
  • Birinci arqument — nəticə (xəta olubsa null),
  • İkinci — istisna (hər şey yaxşıdırsa null).

Nümunə

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) throw new RuntimeException("Təsadüfi xəta!");
    return 100;
});

CompletableFuture<Integer> safeFuture = future.handle((result, ex) -> {
    if (ex != null) {
        System.out.println("Xəta aşkarlandı: " + ex.getMessage());
        return -1;
    }
    return result;
});

safeFuture.thenAccept(r -> System.out.println("Son nəticə: " + r));

Çıxış:

Xəta aşkarlandı: Təsadüfi xəta!
Son nəticə: -1
Son nəticə: 100

handle ondan istifadə etmək lazımdır ki, tapşırıq necə bitməsindən asılı olmayaraq — uğurla və ya xətayla — hərəkət edə biləsiniz. Bu, yekunların universal işləyicisidir: həmişə çağırılır və iki arqument alır — nəticə (hər şey yaxşıdırsa) və istisna (nəsə qaydasında deyilsə).

Metod mərkəzləşdirilmiş şəkildə xətaları jurnallaşdırmaq, zənciri qırmadan susmaya görə dəyər qaytarmaq və ya asinxron ssenarini səliqəli şəkildə tamamlamaq lazım olanda əladır.

Nümunə:

CompletableFuture<Integer> future = CompletableFuture
    .supplyAsync(() -> 10 / 0) // burada xəta baş verəcək
    .handle((result, ex) -> {
        if (ex != null) {
            System.out.println("Xəta: " + ex.getMessage());
            return 0; // susmaya görə dəyər
        }
        return result;
    });

System.out.println(future.join()); // 0 çap edəcək

exceptionally metodundan fərqli olaraq, hansı ki, yalnız xətalara reaksiya verir, handle həmişə işə düşür, hər iki nəticəni bir yerdə emal etməyə və bütün zəncirin hamar axarını qorumağa imkan verir.

4. whenComplete metodu: tamamlandıqdan sonra yan hərəkətlər

Bəzən nəticəni dəyişmək lazım deyil, sadəcə tapşırıq tamamlandıqdan sonra hər hansı bir əməliyyat yerinə yetirmək istəyirik — məsələn, tamamlandığını jurnala yazmaq, uğurlu və ya xətalı olmasından asılı olmayaraq.

Sxem:

CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action)
  • Birinci arqument — nəticə (xəta olduqda null),
  • İkinci — istisna (uğur olduqda null).

Nümunə

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) throw new RuntimeException("Xəta!");
    return 10;
});

future.whenComplete((result, ex) -> {
    if (ex != null) {
        System.out.println("İcra zamanı xəta: " + ex.getMessage());
    } else {
        System.out.println("Uğurla tamamlandı, nəticə: " + result);
    }
});

Vacib fərq:
whenComplete nəticəni və ya xətanı dəyişmir, yalnız əməliyyatı icra edir. Əgər whenComplete daxilində istisna baş verərsə, o, artıq mövcud olan istisnaya əlavə ediləcək.

Nümunə: loglayırıq, amma müdaxilə etmirik

future
    .whenComplete((res, ex) -> {
        System.out.println("Tapşırıq tamamlandı. Xəta? " + (ex != null));
    })
    .thenAccept(r -> System.out.println("İstifadəçi üçün nəticə: " + r));

5. Xüsusiyyətlər və incəliklər

Best practices: CompletableFuture-də xətaları düzgün necə emal etməli

  • Həmişə asinxron tapşırıqlar zəncirlərinə xəta emalı (exceptionally, handle və ya whenComplete) əlavə edin. Əks halda xəta gözə dəyməyə bilər və tətbiq gözlənilməz davranar.
  • Baş axında get() və ya join() metodlarından try-catch olmadan istifadə etməyin — bu, asinxron kodu sinxrona çevirir və bloklanmalara səbəb ola bilər.
  • Xəta zamanı “ehtiyat” dəyər qaytarmaq lazımdırsa — exceptionally və ya handle istifadə edin.
  • Yan təsirlər (loglama, istifadəçiyə bildiriş) üçün — whenComplete istifadə edin.
  • Zəncirlərdə kombinə etmək olar: məsələn, əvvəlcə exceptionally ilə xətanı emal edin, sonra whenComplete ilə loglayın, daha sonra nəticənin emalına davam edin.
  • Unutmayın ki, xəta emal olunmasa, o, növbəti get()/join() çağırışına “axacaq” və tətbiqin dayanmasına səbəb ola bilər.

Metodların ardıcıllığı

  • Əgər exceptionally istifadə edirsinizsə, o, yalnız ondan əvvəl zəncirdə yaranan xətaları tutur.
  • Əgər exceptionally-dən sonra zəncirdə yenidən xəta baş verərsə (məsələn, thenApply mərhələsində), onu ayrıca emal etmək lazımdır.
  • handle universaldır — xəta olsun və ya olmasın həmişə işə düşür.

Metodların birləşdirilməsi

CompletableFuture.supplyAsync(() -> {
    // ...
})
.handle((result, ex) -> {
    if (ex != null) return "Xəta: " + ex.getMessage();
    return result;
})
.whenComplete((res, ex) -> {
    System.out.println("Tapşırıq tamamlandı, nəticə: " + res);
});

Xəta emal olunmasa nə baş verər?

Əgər istisna emal olunmayıbsa və siz get() və ya join() çağırırsınızsa, o, ExecutionException (və ya CompletionException) kimi atılacaq və tətbiq xəta ilə sona çata bilər.

6. CompletableFuture-də xəta emalı zamanı tipik səhvlər

Səhv №1: xəta emalının olmaması. Əgər nə exceptionally, nə handle, nə də whenComplete əlavə etməsəniz, xəta sadəcə növbəti get()/join() çağırışına qədər “itəcək” və bu çağırış ortaya çıxma nöqtəsindən xeyli uzaqda ola bilər.

Səhv №2: baş axında try-catch olmadan get()/join() istifadə etmək. Bu, asinxron kodu sinxrona çevirir və bloklanmalara və ya tətbiqin gözlənilməz dayanmasına səbəb ola bilər.

Səhv №3: işləyicinin məhz harada işə düşdüyünü düzgün başa düşməmək. exceptionally yalnız zəncirdə özündən əvvəlki xətaları tutur. Əgər ondan sonra yenidən xəta baş verərsə, bu metod onu emal etməyəcək.

Səhv №4: xətanı emal etmək, amma dəyər qaytarmamaq. exceptionally və ya handle metodlarında hökmən dəyər qaytarmaq lazımdır, əks halda zəncirin növbəti mərhələsi null alacaq (və ya heç nə almayacaq).

Səhv №5: handle və whenComplete arasında qarışıqlıq. handle nəticəni dəyişə bilər, whenComplete isə yalnız əməliyyat (məsələn, loglama) icra edir. Nəticəni dəyişmək istəyirsinizsə — handle istifadə edin.

Səhv №6: xəta emalı məntiqinin təkrarlanması. Çox vaxt xətaların emalını bir yerdə birləşdirərək kod təkrarlanmasının qarşısını almaq olar — məsələn, mərkəzləşdirilmiş handle və ya ümumi işləyici vasitəsilə.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION