JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Hamıya salam. Əlaqə Omelnitsky Sergey. Bir müddət əvvəl mən JavaScript-də asinxroniya haqqında danışdığım reaktiv proqramlaşdırma üzrə bir axın təşkil etdim. Bu gün mən bu materialı ümumiləşdirmək istərdim.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Ancaq əsas materiala başlamazdan əvvəl bir giriş etmək lazımdır. Beləliklə, təriflərdən başlayaq: yığın və növbə nədir?

Yığın elementləri "son girən, ilk çıxan" LIFO əsasında əldə edilən kolleksiyadır

Növbə elementləri (“ilk girən, birinci çıxan” FİFO) prinsipinə uyğun alınan kolleksiyadır

Yaxşı, davam edək.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

JavaScript tək yivli proqramlaşdırma dilidir. Bu o deməkdir ki, onun yalnız bir icra başlığı və funksiyaların icra üçün növbəyə qoyulduğu bir yığın var. Buna görə də, JavaScript eyni anda yalnız bir əməliyyatı yerinə yetirə bilər, digər əməliyyatlar isə çağırılana qədər stekdə öz növbəsini gözləyəcək.

Zəng yığın sadə dillə desək, proqramda olduğumuz yer haqqında məlumatları qeyd edən verilənlər strukturudur. Funksiyaya tullansaq, onun girişini yığının yuxarı hissəsinə itələyirik. Funksiyadan qayıtdıqda, biz yığından ən yuxarı elementi çıxarırıq və bu funksiyanı çağırdığımız yerdə bitiririk. Bu yığının edə biləcəyi hər şeydir. Və indi çox maraqlı bir sual. JavasScript-də asinxroniya necə işləyir?

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Əslində, yığına əlavə olaraq, brauzerlərdə sözdə WebAPI ilə işləmək üçün xüsusi növbə var. Bu növbənin funksiyaları yalnız yığın tamamilə təmizləndikdən sonra ardıcıllıqla yerinə yetiriləcək. Yalnız bundan sonra onlar icra üçün növbədən yığına yerləşdirilir. Əgər hazırda yığında ən azı bir element varsa, o zaman onlar yığına daxil ola bilməzlər. Məhz buna görə funksiyaların zaman aşımı ilə çağırılması çox vaxt düzgün olmur, çünki funksiya dolu olduqda növbədən yığına keçə bilmir.

Gəlin aşağıdakı nümunəyə nəzər salaq və onu addım-addım nəzərdən keçirək. Gəlin sistemdə nə baş verdiyini də görək.

console.log('Hi');
setTimeout(function cb1() {
    console.log('cb1');
}, 5000);
console.log('Bye');

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

1) Hələlik heç nə baş vermir. Brauzer konsolu təmizdir, zəng yığını boşdur.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

2) Sonra console.log('Hi') əmri zəng yığınına əlavə edilir.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

3) Və yerinə yetirildi

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

4) Sonra console.log('Hi') zəng yığınından silinir.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

5) İndi setTimeout(funksiya cb1() {… }) əmrinə keçək. O, zəng yığınına əlavə olunur.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

6) setTimeout(funksiya cb1() {… }) əmri yerinə yetirilir. Brauzer Web API-nin bir hissəsi olan taymer yaradır. O, geri sayma həyata keçirəcək.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

7) setTimeout(funksiya cb1() {… }) əmri öz işini tamamladı və zəng yığınından silindi.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

8) console.log('Bye') əmri zəng yığınına əlavə edilir.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

9) console.log('Bye') əmri yerinə yetirilir.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

10) console.log('Bye') əmri zəng yığınından silindi.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

11) Ən azı 5000ms keçdikdən sonra taymer bitir və cb1 geri çağırışını geri çağırış növbəsinə qoyur.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

12) Hadisə dövrəsi cb1 funksiyasını geri çağırış növbəsindən götürür və onu zəng yığınına itələyir.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

13) cb1 funksiyası yerinə yetirilir və çağırış yığınına console.log('cb1') əlavə edir.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

14) console.log('cb1') əmri yerinə yetirilir.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

15) console.log('cb1') əmri zəng yığınından silinir.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

16) cb1 funksiyası zəng yığınından çıxarılır.

Dinamikada bir nümunəyə baxaq:

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Yaxşı, biz JavaScript-də asinxroniyanın necə həyata keçirildiyinə baxdıq. İndi asinxron kodun təkamülü haqqında qısaca danışaq.

Asinxron kodun təkamülü.

a(function (resultsFromA) {
    b(resultsFromA, function (resultsFromB) {
        c(resultsFromB, function (resultsFromC) {
            d(resultsFromC, function (resultsFromD) {
                e(resultsFromD, function (resultsFromE) {
                    f(resultsFromE, function (resultsFromF) {
                        console.log(resultsFromF);
                    })
                })
            })
        })
    })
});

JavaScript-də bildiyimiz kimi asinxron proqramlaşdırma yalnız funksiyalarla edilə bilər. Onlar hər hansı digər dəyişən kimi digər funksiyalara ötürülə bilər. Geri çağırışlar belə yarandı. Kədər, melankoliya və kədərə çevrilənə qədər sərin, əyləncəli və qızğındır. Niyə? Bəli, sadədir:

  • Kodun mürəkkəbliyi artdıqca, layihə tez bir zamanda qaranlıq çoxlu iç-içə bloklara - “geri çağırış cəhənnəmi”nə çevrilir.
  • Səhvlərin idarə edilməsi asanlıqla nəzərdən qaçırıla bilər.
  • İfadələri return ilə qaytara bilməzsiniz.

Promise-in gəlməsi ilə vəziyyət bir az yaxşılaşdı.

new Promise(function(resolve, reject) {
    setTimeout(() => resolve(1), 2000);

}).then((result) => {
    alert(result);
    return result + 2;

}).then((result) => {
    throw new Error('FAILED HERE');
    alert(result);
    return result + 2;

}).then((result) => {
    alert(result);
    return result + 2;

}).catch((e) => {
    console.log('error: ', e);
});

  • Kodun oxunuşunu yaxşılaşdıran vəd zəncirləri meydana çıxdı
  • Səhvlərin qarşısını almaq üçün ayrıca bir üsul var idi
  • Promise.all ilə paralel icra əlavə edildi
  • İç içə asinxroniyanı async/await ilə həll edə bilərik

Lakin vədin öz məhdudiyyətləri var. Məsələn, bir vəd, qafla rəqs etmədən, ləğv edilə bilməz və ən əsası, bir dəyərlə işləyir.

Yaxşı, burada reaktiv proqramlaşdırmaya rəvan yaxınlaşırıq. Yorğun? Yaxşısı odur ki, bir neçə qağayı dəmləməyə, beyin fırtınasına gedə və daha çox oxumaq üçün qayıda bilərsiniz. Və davam edəcəyəm.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Reaktiv proqramlaşdırma - məlumat axınına və dəyişikliklərin yayılmasına yönəlmiş proqramlaşdırma paradiqması. Məlumat axınının nə olduğuna daha yaxından nəzər salaq.

// Получаем ссылку на элемент
const input = ducument.querySelector('input');

const eventsArray = [];

// Пушим каждое событие в массив eventsArray
input.addEventListener('keyup',
    event => eventsArray.push(event)
);

Təsəvvür edək ki, bizdə bir giriş sahəsi var. Biz massiv yaradırıq və giriş hadisəsinin hər bir düyməsi üçün hadisəni massivimizdə saxlayacağıq. Eyni zamanda qeyd etmək istərdim ki, massivimiz zamana görə sıralanır, yəni. sonrakı hadisələrin indeksi əvvəlki hadisələrin indeksindən böyükdür. Belə bir massiv sadələşdirilmiş məlumat axını modelidir, lakin hələ axın deyil. Bu massivin etibarlı şəkildə axın adlandırılması üçün o, abunəçilərə yeni məlumatların daxil olması barədə bir şəkildə məlumat verə bilməlidir. Beləliklə, axının tərifinə gəlirik.

Məlumat axını

const { interval } = Rx;
const { take } = RxOperators;

interval(1000).pipe(
    take(4)
)

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Akış verilənlərin dəyişdiyini göstərə bilən zamana görə çeşidlənmiş verilənlər massividir. İndi təsəvvür edin ki, bir hərəkət üçün kodun müxtəlif hissələrində bir neçə hadisəni işə salmaq lazım olan kodu yazmaq nə qədər rahatdır. Biz sadəcə olaraq yayıma abunə oluruq və o, dəyişikliklərin nə vaxt baş verdiyini bizə xəbər verəcəkdir. Və RxJs kitabxanası bunu edə bilər.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

RxJS müşahidə olunan ardıcıllıqlardan istifadə edərək asinxron və hadisəyə əsaslanan proqramlarla işləmək üçün kitabxanadır. Kitabxana əsas növü təqdim edir Müşahidə olunur, bir neçə köməkçi növü (Müşahidəçilər, Planlaşdırıcılar, Subyektlər) və kolleksiyalarda olduğu kimi hadisələrlə işləmək üçün operatorlar (xəritə, filtr, azaltmaq, hər və JavaScript Array-dan oxşarlar).

Bu kitabxananın əsas anlayışlarını anlayaq.

Müşahidə olunan, müşahidəçi, prodüser

Müşahidə olunan, baxacağımız ilk əsas növdür. Bu sinif RxJs tətbiqinin əsas hissəsini ehtiva edir. O, abunə metodundan istifadə etməklə abunə oluna bilən müşahidə olunan axınla əlaqələndirilir.

Observable, sözdə yeniləmələrin yaradılması üçün köməkçi mexanizm tətbiq edir Müşahidəçi. Müşahidəçi üçün dəyərlərin mənbəyi deyilir Istehsalçı. Bu massiv, iterator, veb yuvası, bir növ hadisə və s. ola bilər. Beləliklə deyə bilərik ki, müşahidə olunan İstehsalçı və Müşahidəçi arasında bir dirijordur.

Müşahidə edilə bilən üç növ Müşahidəçi hadisəsini idarə edir:

  • növbəti - yeni məlumatlar
  • xəta - ardıcıllığın bir istisnaya görə dayandırıldığı səhvdir. bu hadisə həm də ardıcıllığın sonunu nəzərdə tutur.
  • tamamlandı - ardıcıllığın sonu haqqında bir siqnal. Bu o deməkdir ki, daha yeni məlumat olmayacaq

Bir demoya baxaq:

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Başlanğıcda 1, 2, 3 və 1 saniyədən sonra dəyərləri emal edəcəyik. 4 alırıq və mövzumuzu bitiririk.

Yüksək səslə düşünmək

Və sonra anladım ki, bu haqda yazmaqdansa, danışmaq daha maraqlıdır. 😀

abunə

Axına abunə olanda yeni sinif yaradırıq abunə, bu bizə metodla abunəlikdən çıxmaq imkanı verir Unsubscribe. Metoddan istifadə edərək abunələri də qruplaşdıra bilərik əlavə etmək. Yaxşı, məntiqlidir ki, istifadə edərək mövzuları ayıra bilərik aradan qaldırılması. Əlavə etmə və silmə üsulları fərqli abunəliyi giriş kimi qəbul edir. Qeyd etmək istərdim ki, biz abunəni ləğv edəndə bütün uşaq abunəliklərindən imtina edirik, sanki onlar abunədən çıxmaq metodunu da çağırıblar. Davam et.

Axın növləri

HOT
TƏCİLİ

İstehsalçı müşahidə edilə biləndən kənarda yaradılmışdır
İstehsalçı müşahidə edilə bilən daxilində yaradılmışdır

Məlumat müşahidə edilə bilənin yaradıldığı anda ötürülür
Məlumat abunə zamanı verilir.

Abunəni ləğv etmək üçün daha çox məntiq lazımdır
Mövzu öz-özünə bitir

Bir-çox əlaqəsindən istifadə edir
Bir-bir münasibətdən istifadə edir

Bütün abunələr eyni dəyərə malikdir
Abunəliklər müstəqildir

Abunə olmadıqda məlumatlar itirilə bilər
Yeni abunə üçün bütün axın dəyərlərini yenidən nəşr edir

Bənzətmə üçün kinoteatrdakı film kimi qaynar bir axını təsəvvür edərdim. Hansı vaxtda gəldin, o andan baxmağa başladın. Soyuq bir axını bundakı zənglə müqayisə edərdim. dəstək. İstənilən zəng edən şəxs cavab verən maşının qeydini əvvəldən axıra kimi dinləyir, lakin siz abunəni ləğv etməklə telefonu bağlaya bilərsiniz.

Qeyd etmək istərdim ki, isti axınlar da var (mən belə bir tərifə çox nadir hallarda rast gəldim və yalnız xarici icmalarda rast gəldim) - bu, soyuq bir axından istiyə çevrilən bir axındır. Sual yaranır - harada istifadə etmək olar)) Təcrübədən bir nümunə verəcəyəm.

Mən Angular ilə işləyirəm. O, rxjs-dən aktiv şəkildə istifadə edir. Serverə məlumat əldə etmək üçün mən soyuq axın gözləyirəm və mən asyncPipe istifadə edərək şablonda bu axını istifadə edirəm. Bu borudan bir neçə dəfə istifadə etsəm, soyuq axının tərifinə qayıdaraq, hər bir boru serverdən məlumat tələb edəcək, ən azı qəribədir. Soyuq axını istiyə çevirsəm, sorğu bir dəfə baş verəcəkdir.

Ümumiyyətlə, axınların növünü başa düşmək yeni başlayanlar üçün olduqca çətindir, lakin vacibdir.

Operatorlar

return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
    .pipe(
        tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
        map(({ data }: TradeCompanyList) => data)
    );

Operatorlar bizə axınlarla işləmək imkanı verir. Onlar Müşahidə olunanda baş verən hadisələri idarə etməyə kömək edir. Ən populyarlarından bir neçəsini nəzərdən keçirəcəyik və operatorlar haqqında daha çox məlumatı faydalı məlumatdakı keçidlərdə tapa bilərsiniz.

operatorları

-nin köməkçi operatoru ilə başlayaq. Sadə bir dəyər əsasında Müşahidə Olunan yaradır.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Operatorlar-filtr

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Filtr operatoru, adından da göründüyü kimi, axın siqnalını süzür. Operator true qaytarırsa, daha da atlayır.

Operatorlar - götürün

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

almaq - Emissiyaların sayının dəyərini alır, bundan sonra axın başa çatır.

Operatorlar-debounceTime

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

debounceTime - çıxış məlumatları arasında müəyyən vaxt intervalına düşən emissiya edilmiş dəyərləri ləğv edir - vaxt intervalı keçdikdən sonra sonuncu dəyəri verir.

const { Observable } = Rx;
const { debounceTime, take } = RxOperators;

Observable.create((observer) => {
  let i = 1;
  observer.next(i++);
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next(i++)
  }, 1000);

 // Испускаем значение раз в 1500мс
  setInterval(() => {
    observer.next(i++)
  }, 1500);
}).pipe(
  debounceTime(700),  // Ожидаем 700мс значения прежде чем обработать
  take(3)
);  

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Operatorlar-takeWhile

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

TakeWhile false qaytarana qədər dəyərlər yayır, sonra axın abunəliyini ləğv edir.

const { Observable } = Rx;
const { debounceTime, takeWhile } = RxOperators;

Observable.create((observer) => {
  let i = 1;
  observer.next(i++);
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next(i++)
  }, 1000);
}).pipe(
  takeWhile( producer =>  producer < 5 )
);  

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Operatorlar-combineLatest

combineLatest birləşdirilmiş operatoru bir qədər dictionary.all ilə oxşardır. Birdən çox axını birləşdirir. Hər bir ip ən azı bir emissiya etdikdən sonra hər birindən massiv kimi ən son dəyərləri alırıq. Bundan əlavə, birləşdirilmiş axınlardan hər hansı bir emissiyadan sonra yeni dəyərlər verəcəkdir.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

const { combineLatest, Observable } = Rx;
const { take } = RxOperators;

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
});

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 750мс
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
});

combineLatest(observer_1, observer_2).pipe(take(5));

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Operatorlar-zip

Zip - hər axından dəyər gözləyir və bu dəyərlər əsasında massiv əmələ gətirir. Əgər dəyər hər hansı bir mövzudan gəlmirsə, o zaman qrup yaranmayacaq.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

const { zip, Observable } = Rx;
const { take } = RxOperators;

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
});

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 750
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
});

const observer_3 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 500
  setInterval(() => {
    observer.next('c: ' + i++);
  }, 500);
});

zip(observer_1, observer_2, observer_3).pipe(take(5));

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Operatorlar - forkJoin

forkJoin də mövzulara qoşulur, lakin o, yalnız bütün mövzular tamamlandıqda bir dəyər verir.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

const { forkJoin, Observable } = Rx;
const { take } = RxOperators;

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
}).pipe(take(3));

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 750
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
}).pipe(take(5));

const observer_3 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 500
  setInterval(() => {
    observer.next('c: ' + i++);
  }, 500);
}).pipe(take(4));

forkJoin(observer_1, observer_2, observer_3);

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Operatorlar-xəritə

Xəritənin çevrilməsi operatoru emit dəyərini yenisinə çevirir.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

const {  Observable } = Rx;
const { take, map } = RxOperators;

Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next(i++);
  }, 1000);
}).pipe(
  map(x => x * 10),
  take(3)
);

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Operatorlar - paylaşın, vurun

Tap operatoru yan təsirləri, yəni ardıcıllığa təsir etməyən hər hansı hərəkətləri etməyə imkan verir.

Paylaşım kommunal operatoru soyuq axını isti axına çevirə bilər.

JavaScript-də asinxron proqramlaşdırma (Callback, Promise, RxJs)

Operatorlar hazırdır. Mövzuya keçək.

Yüksək səslə düşünmək

Sonra çay içməyə getdim. Bezdim bu misallardan 😀

Mövzu ailəsi

Mövzu ailəsi isti iplərin ən yaxşı nümunəsidir. Bu siniflər eyni zamanda müşahidə oluna bilən və müşahidəçi kimi çıxış edən bir növ hibriddir. Mövzu qaynar bir axın olduğundan, onun abunəliyi ləğv edilməlidir. Əsas üsullar haqqında danışırıqsa, bunlar:

  • növbəti - axına yeni məlumatların ötürülməsi
  • səhv - xəta və mövzunun dayandırılması
  • tam - ipin sonu
  • abunə olun - axına abunə olun
  • abunəni dayandırmaq - mövzudan çıxmaq
  • asObservable - müşahidəçiyə çevrilir
  • toPromise - sözə çevrilir

4 5 növ fənni ayırın.

Yüksək səslə düşünmək

Yayımda 4 dedim, amma bir də əlavə etdilər. Necə deyərlər, yaşa və öyrən.

Sadə Mövzu new Subject()- ən sadə fənlər növü. Parametrlər olmadan yaradılmışdır. Yalnız abunədən sonra gələn dəyərləri keçir.

DavranışMövzu new BehaviorSubject( defaultData<T> ) - mənim fikrimcə, mövzuların ən çox yayılmış növü. Giriş standart dəyəri alır. Abunə olarkən ötürülən son buraxılışın məlumatlarını həmişə saxlayır. Bu sinif həmçinin axının cari dəyərini qaytaran faydalı dəyər metoduna malikdir.

ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) - İstəyə görə, ilk arqument kimi özündə saxlayacağı dəyərlər buferinin ölçüsünü, ikinci dəfə isə dəyişikliklərə ehtiyac duyduğumuz zaman götürə bilər.

asyncsubject new AsyncSubject() - abunə zamanı heç nə baş vermir və dəyər yalnız tamamlandıqdan sonra qaytarılacaq. Axının yalnız sonuncu dəyəri qaytarılacaq.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - Sənədləşmə bu barədə susur və mən özüm bunu ilk dəfədir görürəm. Kim bilir nə edir, yaz, əlavə edəcəyik.

vay. Yaxşı, bu gün demək istədiyim hər şeyi nəzərdən keçirdik. Ümid edirəm bu məlumat faydalı oldu. Siz Faydalı Məlumatlar sekmesinde ədəbiyyat siyahısını özünüz oxuya bilərsiniz.

faydalı məlumat

Mənbə: www.habr.com

Добавить комментарий