Kami mempelajari janji berdasarkan spesifikasi Ecmascript. Kenalan

janji pengantar

Halo Mempelajari JavaScript (dan, pada prinsipnya, teknologi lainnya), berbagai pertanyaan selalu muncul, yang utamanya adalah: "Mengapa ia bekerja seperti ini dan bukan sebaliknya?" Dan sangat penting saat ini tidak hanya untuk menemukan jawaban atas pertanyaan, tetapi juga penjelasan yang diterima menanamkan dalam satu sistem pengetahuan yang sudah diperoleh. Kalau tidak, informasi yatim harus dihafalkan atau dilupakan.


Mempelajari sesuatu bersama sangat membantu untuk menemukan jawaban. Ketika seorang siswa / rekan bertanya pertanyaan tentang bagaimana memahami ungkapan - "... hasil dari yang sebelumnya" gagal "dalam janji berikutnya dalam rantai ..." yang tanpa sadar berpikir ... Ini adalah hal yang aneh. Tapi Anda tidak bisa mengatakan lebih baik lagi, apakah itu benar-benar tidak jelas? Anda melihat mata pendamping yang bersih, agak naif dan mengerti - Anda perlu mengatakan sesuatu yang lain. Diinginkan sehingga Anda bahkan tidak perlu menghafal. Untuk informasi baru secara organik sesuai dengan pemikiran manusia yang ada.


Saya tidak akan menjelaskan apa yang kami coba, baca, tonton. Sebagai hasilnya, kami menjadi tertarik pada spesifikasi ECMAScript. Cara membaca dan memahaminya adalah percakapan terpisah (bahkan mungkin posting terpisah). Tetapi cara janji dan perilaku mereka dijelaskan di sana, untuk pertama kalinya memberi kami pemahaman yang holistik dan logis secara koheren tentang topik ini. Apa yang ingin saya bagikan dengan Anda.


Artikel ini untuk pemula. Janji-janji dalam hal spesifikasi naskah ECMAS akan dibahas di sini. Aku tahu kedengarannya aneh, tetapi apa adanya.


Obyek yang dijanjikan: filosofi, presentasi teknis, kemungkinan kondisi


Saya telah memperhatikan lebih dari sekali bahwa pelatihan pemrograman berkualitas tinggi harus terdiri dari 2 bagian. Ini adalah pemahaman filosofis dari ide tersebut, dan baru kemudian implementasi teknisnya. Artinya, logika manusia biasa, yang dibimbing siswa ketika membuat keputusan, sangat memudahkan pemahaman tentang implementasi teknis dari keputusan ini. Karena itu, kita mulai dengan apa janji itu dalam hidup, dan bagaimana kita menghubungkannya dengan itu? Dan kemudian kita akan melihat: bagaimana contoh janji akan diimplementasikan dalam kode. Pertimbangkan gambar-gambar berikut (Gbr. 1, 2, 3).


Janji negara
Gambar 1. ([[PromiseState]] - sebagai hasil dari janji)

Hasil Janji
gbr 2. ([[PromiseResult]] - sebagai informasi yang terkait dengan hasil dari janji yang terpenuhi atau tidak terpenuhi)

Reaksi janji
Gambar 3. ([[[Janji Janji]], [[Janji Janji]] - sebagai konsekuensi yang terjadi setelah memenuhi atau tidak memenuhi janji)

Kita melihat bahwa konsep janji berdiri di atas 3 pilar. 1) Apakah janji itu terpenuhi sama sekali? 2) Informasi tambahan apa yang dapat kita ekstrak setelah memenuhi atau menolak janji? 3) Apa akibatnya jika janji kita positif atau negatif?


Secara teknis, janji adalah entitas biasa yang diekspresikan melalui tipe data seperti objek. Entitas ini memiliki nama / kelas Janji. Objek yang lahir dari kelas ini memiliki Promise.prototype dalam rantai prototipe mereka. Dan entitas ini entah bagaimana harus terhubung dengan semua "informasi dari kehidupan" yang kami periksa di atas. Spesifikasi ECMAScript memberikan informasi ini dalam janji bahkan pada tingkat yang lebih rendah dalam abstraksi daripada JavaScript itu sendiri. Misalnya, di level C ++. Dengan demikian, dalam objek janji ada tempat baik di bawah status, dan di bawah hasilnya, dan di bawah konsekuensi dari janji. Lihatlah apa isi janji ECMAScript (Gambar 4).


Bidang janji
Gambar 4. (Bidang internal objek janji sesuai dengan spesifikasi skrip ECMAS)

Warna baru apa yang dimainkan oleh ungkapan “janji tidak berarti menikah” dalam pengertian seorang programmer? 1) [[PromiseState]]. Seseorang belum menikah. 2) [[PromiseResult]]. Karena dia tidak punya cukup uang untuk pernikahan. 3) [[PromiseRejectReactions]]. Akibatnya, ia memiliki banyak waktu luang yang ia habiskan untuk pengembangan diri 4) [[PromiseFulfillReactions]]. Mengapa seseorang membutuhkan rencana B ketika ia telah memilih rencana A?


Ya, ada bidang kelima [[PromiseIsHandled]]. Itu tidak terlalu penting bagi kita semua, dan kita tidak akan lagi mengoperasikannya di masa depan. Singkatnya: ada sinyal yang disimpan kepada penerjemah tentang apakah janji itu ditolak oleh programmer atau tidak. Jika tidak, tolak janji mentah ditafsirkan oleh mesin JS sebagai kesalahan. Untuk yang tidak sabar: jika programmer tidak menggantungkan fungsi Promise.prototype.then () sebagai pemanggil-fungsi-penangan kedua dari status yang ditolak dari janji, maka status objek yang “ditolak” dari objek akan menunjukkan kepada Anda kesalahan merah di konsol pengembang.


Pernahkah Anda memperhatikan bahwa bidang objek janji terlampir dalam "[[" dan "]]"? Ini menekankan bahwa programmer JS tidak memiliki akses langsung ke informasi ini. Hanya melalui alat / perintah / API khusus, seperti perintah Promise.prototype.then (). Jika Anda memiliki keinginan yang tak tertahankan untuk mengendalikan "dapur ini" secara langsung, selamat datang di klub standar spesifikasi EcmaScript.


Sebuah komentar singkat di akhir sub-bab ini. Jika dalam hidup di negara kita janji sebagian dapat dipenuhi, maka dalam EcmaScript - tidak. Artinya, jika seseorang berjanji untuk memberi satu juta, dan memberi 950 ribu, maka dalam hidup, mungkin dia adalah mitra yang dapat diandalkan, tetapi untuk JavaScript debitur seperti itu akan masuk daftar hitam melalui [[PromiseState]] === "ditolak". Objek Janji mengubah negaranya dengan jelas dan hanya sekali. Bagaimana ini diterapkan secara teknis sedikit kemudian.


Promise Designer, filosofinya. Pelaksana fungsi callback seperti "pelaksana" janji. Skema interaksi: Janji (konstruktor) - pelaksana (panggilan balik) - janji (objek)


Jadi, kami menemukan bahwa janji adalah entitas yang secara teknis adalah objek JS dengan bidang internal tersembunyi khusus, yang pada gilirannya memberikan pengisian filosofis dengan makna kata "janji".


Ketika seorang pemula membuat objek janji untuk pertama kalinya, gambar berikut menunggunya (Gbr. 5).


salah menciptakan objek janji
Gambar 5. (Pertama kali kami secara intuitif membuat objek janji)

Apa yang salah dan mengapa kesalahan adalah pertanyaan standar. Dalam menjawabnya, lebih baik untuk membawa analogi dari kehidupan lagi. Misalnya, beberapa orang menyukai "lonceng kosong" di sekitar kita: yang hanya berjanji, tetapi tidak melakukan apa pun untuk memenuhi pernyataan mereka (politik tidak masuk hitungan). Kami jauh lebih baik pada orang-orang yang, setelah janji mereka, punya rencana dan segera mengambil tindakan untuk mencapai hasil yang dijanjikan.


Jadi filosofi ECMAScript menyiratkan bahwa jika Anda membuat janji, maka segera tunjukkan bagaimana Anda akan memenuhinya. Programmer perlu menyusun rencana tindakannya dalam bentuk parameter fungsi, yang Anda berikan kepada konstruktor Janji. Eksperimen selanjutnya terlihat seperti ini (Gbr. 6).


Konstruktor janji menggunakan pelaksana
Gambar 6. (Membuat objek janji, meneruskan fungsi eksekutor ke konstruktor Janji)

Dari keterangan ke gambar, kita melihat bahwa fungsi (parameter konstruktor Janji) memiliki nama sendiri - pelaksana. Tugasnya adalah untuk mulai memenuhi janji dan, lebih baik, membawanya ke semacam kesimpulan logis. Dan jika pemrogram dapat menulis kode apa pun di pelaksana, lalu bagaimana pemrogram dapat memberi sinyal kepada JS bahwa semuanya - pekerjaan sudah selesai - dapatkah Anda melihat hasil dari janji tersebut?


Marker atau sinyal yang membantu programmer untuk menginformasikan bahwa janji telah selesai diteruskan secara otomatis ke pelaksana-parameter dalam bentuk argumen yang khusus dibentuk oleh JavaScript. Parameter ini dapat dipanggil sesuka Anda, tetapi paling sering Anda akan menamainya dengan nama seperti res dan rej. Dalam spesifikasi skrip ECMAS, nama lengkapnya adalah fungsi penyelesaian dan fungsi tolak. Penanda fungsi ini memiliki karakteristiknya sendiri, yang akan kita bahas nanti.


Untuk memahami informasi baru, pendatang baru diundang untuk secara mandiri menyandikan pernyataan berikut: "Saya berjanji bahwa saya dapat membagi satu nomor menjadi yang lain dan memberikan jawaban, jika saja pembagi itu tidak nol." Begini tampilannya kode (gbr. 7).


janji tugas: pembagian dengan nol
Gambar 7. (Solusi masalah membagi 2 angka melalui janji)

Sekarang Anda dapat menganalisis hasilnya. Kami melihat bahwa untuk kedua kalinya konsol browser menunjukkan objek Promis dengan cara yang menarik. Yaitu: 2 bidang tambahan ditunjukkan dalam tanda kurung ganda. Anda dapat dengan aman menggambar analogi antara [[PromiseState]] dan [[PromiseStatus]], terpenuhi dan diselesaikan, [[PromiseValue]] dan [[PromiseResult]]. Ya, browser itu sendiri mencoba memberi tahu programmer tentang keberadaan dan nilai bidang internal objek janji. Kita juga melihat sistem yang terhubung dari objek janji, fungsi pelaksana, fungsi khusus-callback-token dan res.


Agar siswa / pasangan menjadi lebih santai dalam materi ini, ia ditawari kode berikut (Gbr. 8). Penting untuk menganalisis dan menjawab pertanyaan-pertanyaan berikut.


janji tugas: pembagian dengan nol. Versi alternatif
Gambar 8. (Variasi solusi untuk masalah membagi 2 angka melalui janji)

Akankah kodenya bekerja? Di mana fungsi eksekutor di sini dan apa namanya? Apakah nama "wantToDivide" sesuai dalam kode ini? Apa fungsi ikat kembali setelah itu sendiri? Mengapa argumen diteruskan ke fungsi bind hanya di tempat kedua dan ketiga? Di mana fungsi khusus menyelesaikan fungsi dan menolak fungsi menghilang? Bagaimana cara memasukkan nomor urut nomor 1 dan nomor 2 ke dalam “rencana pemenuhan janji”? Berapa banyak elemen dalam argumen pseudo-array? Apakah mungkin untuk memulihkan dari memori seperti apa jawabannya pada konsol browser?


Pembaca diajak berpikir tentang jawaban atas pertanyaan itu sendiri. Juga
bereksperimen dalam kode. Untungnya, kodenya kecil dan gagasan tugasnya sederhana. Ya, ada pertanyaan tentang janji dan pengetahuan umum tentang JavaScript. Apa yang harus dilakukan, di mana pun kami menunggu kejutan yang mencegah kami bersantai. Segera setelah semuanya menjadi jelas bagi Anda, Anda dapat melanjutkan.


Lihat / salin kode
let number1 = Number(prompt("input number 1")); let number2 = Number(prompt("input number 2")); let wantToDivide = function() { if (arguments[1] === 0) { arguments[3]("it is forbidden to divide by zero"); return; } let result = arguments[0] / arguments[1]; arguments[2](result); }; let myPromise = new Promise(wantToDivide.bind(null, number1, number2)); console.log(myPromise); 


Pertimbangkan argumen eksekutor-a: selesaikan dan tolak fungsi


Jadi, kami minum kopi - kami melanjutkan. Mari kita pertimbangkan lebih detail fungsi khusus fungsi penyelesaian dan fungsi tolak, yang secara otomatis dihasilkan oleh JavaScript untuk menerjemahkan janji objek ke keadaan terpenuhi atau ditolak, yang melambangkan akhir dari janji.


Sebagai permulaan, mari kita coba melihatnya hanya di konsol pengembang (Gbr. 9).


fungsi tekad penelitian
Gambar 9. (Menjelajahi fungsi fungsi penyelesaian - res)

Kita melihat bahwa fungsi resolusinya adalah fungsi yang mengambil satu argumen (panjang properti === 1). Dan prototipe-nya adalah Function.prototype.


Ok, mari kita lanjutkan eksperimen. Dan apa yang akan terjadi jika kita menghapus tautan ke fungsi tekad / tolak dari pelaksana ke lingkup eksternal? Akankah sesuatu pecah (gbr. 10)?


eksternal contol objek janji
Gambar 10. (Kami menerjemahkan janji myPromise ke status terpenuhi di luar janji)

Tidak ada yang luar biasa. Fungsi sebagai subspesies objek dalam JavaScript dilewatkan dengan referensi. Semuanya berjalan sesuai harapan kami. Variabel dari penutupan outerRes mendapat referensi ke fungsi penyelesaian resolusi kami. Dan kami menggunakan fungsinya untuk menempatkan janji dalam kondisi terpenuhi di luar pelaksana itu sendiri. Contoh yang sedikit dimodifikasi berikut ini menunjukkan ide yang sama, jadi lihatlah kodenya dan pikirkan dalam kondisi apa dan dengan nilai apa myPromise1 dan myPromise2 nantinya (Gbr. 11). Kemudian Anda dapat memeriksa asumsi Anda di bawah spoiler.


janji tugas. Pertanyaan Gambar 11. (Tugas refleksi. Dalam kondisi apa dan dengan nilai apa janji-janji myPromise1 dan myPromise2 ada di konsol pengembang?)

Jawaban untuk masalah pada Gambar 11 (Gambar 12).
janji tugas. Jawab
Gambar 12. (Jawaban untuk masalah pada Gambar 11)

Dan sekarang Anda dapat memikirkan satu pertanyaan menarik. Tetapi bagaimana fungsi tekad / tolak selalu tahu persis janji mana yang harus diterjemahkan ke dalam kondisi yang disyaratkan? Kita beralih ke algoritma dalam spesifikasi , yang menjelaskan bagaimana fungsi-fungsi ini dibuat (Gbr. 13).


membuat fungsi penyelesaian
Gambar 13. (Fitur membuat fungsi penyelesaian untuk satu objek janji tertentu)

Poin-poin penting untuk diperhatikan:


  • pada saat pembuatan fungsi resol / menolak, mereka secara kaku melekat pada satu-satunya objek janji yang sesuai dengannya
  • fungsi resol / menolak sebagai tipe data objek memiliki bidang tersembunyi mereka sendiri [[Janji]] dan [[Sudah Diselesaikan]], yang memberikan semua orang dengan logika intuitif yang akrab yang a) - menyelesaikan fungsi sendiri menerjemahkan objek janji ke dalam kondisi yang diperlukan; dan fakta bahwa b) suatu janji tidak dapat ditransfer ke negara bagian lain jika setidaknya sekali fungsi putuskan atau tolak dipanggil. Algoritma ini dapat diwakili oleh gambar berikut (Gbr. 14).

    menyelesaikan fungsi dan menjanjikan objek
    Gambar 14. (Bidang fungsi tersembunyi fungsi tekad dan fungsi tolak)

    Algoritma yang menggunakan informasi ini dari bidang tersembunyi tidak akan dipertimbangkan sekarang, karena mereka bertele-tele dan lebih kompleks. Kita masih perlu mempersiapkan mereka secara teori dan moral. Untuk saat ini, saya hanya bisa mengkonfirmasi pemikiran Anda: “Wow, betapa sederhananya itu ternyata. Mungkin, pada setiap resolusi / resolusi janji objek, bendera "objek" {[[Nilai]]: false} akan diperiksa. Dan jika itu benar, kami menghentikan proses menerjemahkan janji ke negara lain dengan pengembalian sederhana. " Ya - itulah tepatnya yang terjadi. Tampaknya Anda dapat menjawab pertanyaan berikut dengan benar tanpa masalah. Apa yang akan terjadi pada konsol pengembang (Gbr. 15)?


    expertiment dengan menghubungkan fungsi penyelesaian dan objek janji
    Gambar 15. (Eksperimen yang menunjukkan hubungan antara fungsi resol dan menolak dengan satu objek janji tertentu)

    Algoritma untuk membuat objek janji sesuai dengan spesifikasi skrip ECMAS


    Pertimbangkan saat menyihir ketika ia dilahirkan ke dunia - objek janji penuh (Gbr. 16).


    janji penciptaan di ecmascript
    Gambar 16. (Algoritma untuk membuat objek janji dari spesifikasi EcmaScript)

    Seharusnya tidak ada pertanyaan rumit yang muncul saat melihatnya:

    • Konstruktor janji harus dipanggil dalam mode konstruktor, dan bukan hanya pemanggilan fungsi
    • Janji konstruktor membutuhkan fungsi pelaksana
    • buat objek JavaScript dengan bidang tersembunyi tertentu
    • inisialisasi bidang tersembunyi dengan beberapa nilai awal
    • buat tekad dan tolak fungsi yang terkait dengan objek janji
    • kami memanggil fungsi eksekutor untuk dieksekusi, lewat di sana sudah dihasilkan fungsi token token dan tolak fungsi sebagai argumen
    • jika selama eksekusi eksekutor - ada yang tidak beres, letakkan objek janji kita di negara yang ditolak
    • kembali ke variabel objek janji janji lahir.

    Saya tidak tahu apakah ini merupakan penemuan untuk Anda bahwa algoritma pelaksana fungsi dijalankan di sini dan sekarang, dalam mode sinkron normal, bahkan sebelum sesuatu ditulis ke variabel di sebelah kiri konstruktor Janji. Tetapi pada waktunya bagi saya itu menjadi wahyu.


    Karena kami telah menyinggung topik sinkronisasi dan asinkron, berikut adalah kode berikut untuk Anda “pikirkan” atau untuk eksperimen. Pertanyaan: Setelah melihat beberapa kreasi programmer Dima, dapatkah Anda menjawab apa arti game yang disandikan di bawah ini?


     function randomInteger(min, max) { return Math.floor(min + Math.random() * (max + 1 - min)); } function game() { let guessCubeNumber = Number(prompt("Throw dice? Guess number?", 3)); console.log("throwing dice ... wait until it stop"); let gameState = new Promise(function(res, rej) { setTimeout(function() { let gottenNumberDice = randomInteger(1, 6); gottenNumberDice === guessCubeNumber ? res("you win!") : rej(`you loose. ${gottenNumberDice} points dropped on dice`); }, 3000); }); return gameState; } console.log(game()); 

    Tentu saja, ini adalah emulasi dari roll mati. Bisakah pengguna menebak nomor yang terjatuh atau tidak? Lihat bagaimana setTimeout terintegrasi secara asinkron secara organik ke dalam eksekutor sinkron - dalam rencana kami, putar dadu dan temukan nomor yang terjatuh. Bagaimana seseorang dapat menginterpretasikan hasil di konsol pengembang dengan cara khusus (Gbr. 17)?


    Jika kita mencoba melihat janji sampai kubus berhenti (3000 ms ditunjukkan dalam kode), kita akan melihat bahwa janji itu masih dalam keadaan menunggu: permainan belum selesai, kubus belum berhenti, tidak ada nomor yang keluar. Jika kita mencoba melihat objek janji setelah kubus berhenti, kita akan melihat informasi yang sangat spesifik: apakah pengguna menang (menebak nomornya), atau kalah dan mengapa (nomor berapa yang benar-benar jatuh).


    game janji - melempar dadu
    Gambar 17. (Status janji suatu objek ketika ada operasi asinkron dalam fungsi eksekutor)

    Jika Anda tertarik pada contoh ini, atau jika Anda ingin menebak jumlah kubus terbalik, Anda dapat menyalin kode dan melakukan percobaan. Berani!


    Reaksi janji sebagai konsekuensi dari janji yang terpenuhi


    Seperti yang Anda lihat pada Gambar 14, konsekuensi dari penyelesaian / penyelesaian janji suatu objek ditandatangani sebagai "+ reaksi" dan "-reaksi". Istilah resmi untuk kata-kata ini dari spesifikasi ECMAScript adalah reaksi yang menjanjikan. Diasumsikan bahwa dalam artikel berikut topik ini akan dipertimbangkan secara rinci. Untuk saat ini, kami membatasi diri pada gagasan umum tentang apa itu reaksi janji, sehingga istilah ini dapat dikaitkan dengan benar dengan makna filosofis dari kata ini dan pelaksanaan teknisnya.


    Seperti yang kita ingat, sebuah janji mungkin memiliki konsekuensi, tetapi mungkin tidak. Apa konsekuensinya? Ini adalah tindakan yang akan terjadi beberapa waktu kemudian: setelah janji dipenuhi. Dan karena ini adalah tindakan, konsekuensinya dapat diekspresikan oleh fungsi JavaScript normal. Beberapa fungsi akan dieksekusi jika berhasil menyelesaikan janji (+ reaksi); fungsi lain - dalam kasus ketika janji masuk ke negara yang ditolak (-reaksi). Secara teknis, fungsi-fungsi ini (konsekuensi) diteruskan dalam argumen ketika metode Promise.prototype.then () dipanggil.


    Dengan demikian, bagian penting dari reaksi janji adalah tindakan asinkron yang dilakukan di masa depan. Ada komponen penting kedua dari reaksi janji - ini adalah janji yang baru dibuat yang dikembalikan setelah perintah Promise.prototype.then () dieksekusi. Ini karena konsekuensinya mempengaruhi janji-janji lain. Misalnya, ada janji untuk membeli mobil, tetapi hanya setelah janji untuk mendapatkan sejumlah uang terpenuhi. Satu janji terpenuhi - konsekuensinya berhasil - sekarang yang kedua dapat dipenuhi.


    Bahkan, reaksi janji mengikat janji satu sama lain dalam interval waktu tertentu. Penting untuk diingat bahwa reaksi diproses secara otomatis. Panggilan fungsi - konsekuensi dari penyelesaian janji - dibuat oleh mesin JS, bukan programmer (Gbr. 18). Dan, karena reaksi itu berkaitan erat dengan objek janji (janji) itu sendiri, logis untuk mengasumsikan bahwa algoritma reaksi janji menggunakan bidang internal mereka dalam logika mereka. Dan lebih baik untuk mengetahui tentang semua nuansa ini agar dapat mengendalikan logika asinkron secara sadar berdasarkan janji.


    reaksi janji dalam metode ()
    Gambar 18. (Konsekuensi penyelesaian janji dicatat oleh fungsi callback dalam metode then (). Callback akan dipanggil secara asinkron secara otomatis oleh mesin JS)

    Untuk meringkas


    1) Kami berkenalan dengan janji-janji dalam JavaScript, filosofi dan eksekusi teknis mereka. Semua ini diimplementasikan menggunakan bidang janji internal khusus objek: [[PromiseState]], [[PromiseValue]], [[PromiseFulFillReactions]], [[PromiseRejectReactions]].


    2) Programmer diberi kesempatan untuk memenuhi janjinya melalui fungsi pelaksana, disahkan sebagai argumen kepada konstruktor Janji.


    3) Batas-batas janji terpenuhi atau tidak terpenuhi ditentukan oleh fungsi penanda khusus, fungsi penyelesaian dan fungsi penolakan, sering kali dalam kode yang disebut res dan rej. Fungsi-fungsi ini secara otomatis dibuat oleh JavaScript dan diteruskan dalam argumen ke pelaksana.


    4) fungsi penyelesaian dan fungsi penolakan selalu memiliki objek janji yang terkait dengannya, serta bidang khusus umum {[[Nilai]]: false}, yang memastikan bahwa janji tersebut diselesaikan hanya satu kali.


    5) [[PromiseFulFillReactions]] dan [[PromiseRejectReactions]] adalah bidang internal objek janji yang menyimpan konsekuensi penyelesaian janji, bagian penting yang merupakan fungsi asinkron khusus yang ditentukan melalui metode objek janji Promise.protot.then ().


    PS


    Artikel ini disiapkan sebagai abstrak dari sesi video grup InSimpleWords. Ada "pelajaran video" yang cukup dan masih ada bahan untuk membuat catatan. Pertanyaan lain adalah apakah akan menarik bagi anggota masyarakat untuk membaca artikel apa tentang janji secara berturut-turut. Menunggu komentar Anda.

Source: https://habr.com/ru/post/id478938/


All Articles