├── README.md ├── examples ├── 1.js ├── 2.js ├── 3.js └── 4.js └── run.md /README.md: -------------------------------------------------------------------------------- 1 | # ES6 generator and ES7 async functions (TR) 2 | Bu repository altında ES6'da bulunan generatör fonksiyonlarını ve ES7'de bulunan async fonksiyonlarını inceleyeceğiz. 3 | 4 | # ES6 - Generatörler 5 | 6 | ## Nedir bu generatorler? 7 | Generatörler adlandırıldığı gibi bir takım fonksiyondur. Normal bir fonksiyondan farklı olarak çalışma zamanından kaldığı yerden devam etme özelliği taşırlar. Bu sayede bir işi üretirken bir anda döndürmek yerine parça parça döndürebilme yeteneğine sahiptirler. 8 | 9 | Generatör fonksiyonumuzu tanımlarken normal fonksiyona ek, `*` (yıldız) karakterini kullanmamız gerekiyor. 10 | 11 | 12 | ```ts 13 | function* generator() { 14 | 15 | } 16 | ``` 17 | 18 | Bu generator fonksiyonumuzu çağırdığımız taktirde generator'ün gövdesinde tanımlı olan kod parçası çalışmaz. 19 | 20 | 21 | ```ts 22 | function* generator() { 23 | console.log("merhaba"); 24 | } 25 | 26 | generator(); 27 | ``` 28 | 29 | Bunun yerine generator fonksiyonu özel başka bir obje döndürüyor. Eğer dönen bu objenin `.next()` metodunu çağırırsanız merhaba yazısını görebileceksiniz. 30 | 31 | ```ts 32 | function* generator() { 33 | console.log("merhaba"); 34 | } 35 | 36 | var g = generator(); 37 | g.next(); // "merhaba" yazıldı ve {value: undefined, done: true} döndürüldü 38 | ``` 39 | 40 | Gördüğünüz gibi metod başka bir obje döndürdü. Burada `value` undefined olarak atanmış ve `done` ise true. `done` ifadesi generatör fonksiyonun tüm işlevinin bittiğini gösteriyor. `value` ise return edilen değeri getiriyor. 41 | 42 | Buraya kadar normal fonksiyonun dışında farklı bir işlem göremedik ancak işler daha yeni kızışmaya başladı. 43 | 44 | ## Yield 45 | 46 | Generatörlerin `done` değeri döndürdüğünü gördük, demek ki biz generatörleri bir şey kullanarak durdurabiliyoruz. `yield` bu işi yapmamızı sağlayan özelliktir. 47 | 48 | `yield` tıpkı return gibi fonksiyondan çıkışı sağlar ancak returnun aksine çıkılan bu fonksiyona tekrar girişi mümkün kılar. Sadece bu da değil; çıkılan noktaya dışardan değer girilebilmesinide sağlar. 49 | 50 | ```ts 51 | function* generator() { 52 | console.log("merhaba"); 53 | yield; 54 | } 55 | 56 | var g = generator(); 57 | g.next(); // "merhaba" yazıldı ve {value: undefined, done: false} döndürüldü 58 | ``` 59 | 60 | Gördüğünüz gibi bu sefer `done` değeri false oldu. Eğer bir defa daha `.next()` metodunu çalıştırırsak; 61 | 62 | ```js 63 | function* generator() { 64 | console.log("merhaba"); 65 | yield; 66 | } 67 | 68 | var g = generator(); 69 | g.next(); // "merhaba" yazıldı ve {value: undefined, done: false} döndürüldü 70 | g.next(); // {value: undefined, done: true} döndürüldü 71 | ``` 72 | 73 | `done` değeri `true` olacak. `.next()` metodu çağrıldığında `yield` ifadesine gelinceye kadar tüm kodlar sırayla çalıştırılır. `yield` ifadesi geldiğinde eğer return gibi sağında bir değer işlem varsa yapılır, döndürdüğü değer ise `.next()` methodunun döndürdüğü objenin `value` property'sine yazılır. 74 | 75 | Eğer bir değer döndürmek istersek `yield`'in sağına yazabiliriz. 76 | 77 | ```ts 78 | function* generator() { 79 | console.log("merhaba"); 80 | yield 8; 81 | yield 9; 82 | return 10; 83 | } 84 | 85 | var g = generator(); 86 | g.next(); // {value: 8, done: false} 87 | g.next(); // {value: 9, done: false} 88 | g.next(); // {value: 10, done: true} 89 | ``` 90 | 91 | Bu fikirden yola çıkarak `fibonacci sayılarını` generate edebiliriz. 92 | 93 | ```ts 94 | function* fibonacci() { 95 | let a = 1, b = 1; 96 | for(;;) { 97 | yield a + b; 98 | let t = b; 99 | b = a + b; 100 | a = t; 101 | } 102 | } 103 | 104 | var g = fibonacci(); 105 | g.next(); // {value: 2, done: false} 106 | g.next(); // {value: 3, done: false} 107 | g.next(); // {value: 5, done: false} 108 | g.next(); // {value: 8, done: false} 109 | ``` 110 | 111 | Bu generatorümüzün herhangi bir sonu yok. Yani `done` asla true olmayacak. 112 | 113 | ## for-of altında generatörler 114 | 115 | Generatörlerimizi `for-of` syntaxı içinde kullanmamız mümkündür. Ancak bir önceki örnekte olduğu gibi sonsuz olmasından kaçının. Her bir iteration'da `.next()` çağrısı yapılacak ve döndürülen değer değişkene yansıtılacak. For gövdesindeki işler tamamlandıktan sonra tekrar `.next()` çağrısı yapılacak. `done` `true` olduğu an döngü kırılacak. 116 | 117 | ```ts 118 | function* fibonacci(limit) { 119 | let a = 1, b = 1; 120 | while(limit--) { 121 | console.log("bir sonraki :" + (a + b)); 122 | yield a + b; 123 | let t = b; 124 | b = a + b; 125 | a = t; 126 | } 127 | console.log("bitti"); 128 | } 129 | 130 | for(sayi of fibonacci(10)) { 131 | console.log(sayi); 132 | } 133 | ``` 134 | 135 | Bu kodumuzun konsol çıktısı; 136 | 137 | ```ts 138 | bir sonraki :2 139 | 2 140 | bir sonraki :3 141 | 3 142 | bir sonraki :5 143 | 5 144 | bir sonraki :8 145 | 8 146 | bir sonraki :13 147 | 13 148 | bir sonraki :21 149 | 21 150 | bir sonraki :34 151 | 34 152 | bir sonraki :55 153 | 55 154 | bir sonraki :89 155 | 89 156 | bir sonraki :144 157 | 144 158 | bitti 159 | ``` 160 | 161 | Bir örnek daha verelim. for-of syntaxını dizilerin değerlerini okurken kullanıyoruz. 162 | 163 | ```ts 164 | let dizi = [1, 2, 3]; 165 | for (let eleman of dizi) { 166 | console.log(eleman); // 1 .. 2 .. 3 167 | } 168 | ``` 169 | 170 | Ancak objelerin key ve value'leri arasında gezemiyoruz. 171 | 172 | ```ts 173 | let obje = {a: 1, b: 2}; 174 | for (let eleman of obje) { // Uncaught TypeError: undefined is not a function 175 | console.log(eleman); 176 | } 177 | ``` 178 | 179 | Bu problemi generator fonksiyonuyla çözebiliriz, 180 | 181 | ```ts 182 | function* entries(obj) { 183 | for (let key of Object.keys(obj)) { 184 | yield [key, obj[key]]; 185 | } 186 | } 187 | 188 | let obje = {a: 1, b: 2}; 189 | for (let [key, value] of entries(obje)) { 190 | console.log(key, value); // a, 1 .. b, 2 191 | } 192 | ``` 193 | 194 | ## next, throw ve return 195 | `.next()` metodunun bir parametre girişi vardır. Bu girişten vereceğimiz sayı ile `yield` alanına sayı göndermemiz mümkündür. 196 | 197 | ```ts 198 | function* say() { 199 | var baslangic = yield; 200 | console.log(baslangic); 201 | } 202 | 203 | var s = say(); 204 | s.next(); // yield alanına kadar next yapıyoruz. 205 | s.next(3); // 3 206 | ``` 207 | 208 | `.throw()` metodu tıpkı next gibi kodun bulunduğu yerde `throw` yapar. 209 | 210 | ```ts 211 | function* say() { 212 | try { 213 | yield Math.random(); 214 | } catch(e) { 215 | console.error(e); // 0.5'ten büyük olmamalı 216 | } 217 | } 218 | 219 | var s = say(); 220 | if(s.next().value > 0.5) { 221 | s.throw(new Error("0.5'ten büyük olmamalı")); 222 | } 223 | ``` 224 | 225 | `.return()` metodu generatorü sonlandırarak ilk parametredeki değeri value olarak döndürür. 226 | 227 | ```ts 228 | function* say() { 229 | console.log(1); 230 | yield 1; 231 | console.log(2); 232 | yield 2; 233 | console.log(3); 234 | yield 3; 235 | } 236 | 237 | var s = say(); 238 | s.next(); // {value: 1, done: false} 239 | s.return(4); // {value: 4, done: true} 240 | s.next(); // {value: undefined, done: true} 241 | ``` 242 | 243 | ## Yield ve asenkron işler 244 | 245 | Bence tüm bu olayların dışında `yield`'ın en güzel olayı asenkron işleri yapmakta çatı sağlaması. Şimdi teorik olarak düşünelim. `yield` bir fonksiyon generatörünü durduruyor. Sadece durdurmakla kalmıyor bize veride veriyor. Biz `yield`'da `promise` versek, ve bu `promise` bittiğinde başka bir next çağırmasını sağlasak callbacklerden kurtulmuş olmaz mıyız? 246 | 247 | ```ts 248 | function* asenkron() { 249 | yield new Promise(function(resolve) { 250 | setTimeout(function() { 251 | resolve(); 252 | }, 1000); 253 | }); 254 | console.log("merhaba"); 255 | } 256 | 257 | var s = asenkron(); 258 | var promise = s.next().value; // {value: Promise, done: false} 259 | promise.then(function() { 260 | s.next(); 261 | }); 262 | ``` 263 | 264 | Bu kod ile 1 saniye bekledikten sonra merhaba yazdırdık. Öyle bir fonksiyon tasarlayalım ki bu işi bizim yerimize o yapsın. 265 | 266 | ```ts 267 | function run(g) { 268 | var i = g.next(); // bir sonraki değeri getir 269 | if (!i.done) { // eğer bitmediyse 270 | if (i.value && i.value.constructor == Promise) { // dönen değer promise ise 271 | i.value.then(function (data) { // promise'in bitmesini bekle 272 | run(g); // yeniden kendini çağır 273 | }).catch(function (e) { // hata oluştuysa 274 | g.throw(e); // ilgili yerde hata fırlat 275 | }); 276 | } else { // eğer promise değilse sadece devam et 277 | run(g); 278 | } 279 | } 280 | } 281 | ``` 282 | 283 | Tabi ki bu kodu inanılmaz derecede basit tuttum ve bu yüzden de bellek problemleri var. Kullanmak için tek yapılması gereken `run(generator())` çağrısı yapmak. Daha önce tanımladığımız delay işlemini fonksiyon halinede getirelim. 284 | 285 | ```ts 286 | function delay(time) { 287 | return new Promise(function(resolve) { 288 | setTimeout(function() { 289 | resolve(); 290 | }, time); 291 | }); 292 | } 293 | ``` 294 | 295 | ```ts 296 | function* merhaba() { 297 | yield delay(1000); 298 | console.log("merhaba"); 299 | 300 | yield delay(1000); 301 | console.log("dünya"); 302 | } 303 | run(merhaba()); 304 | ``` 305 | 306 | 1 saniye aralıklarla merhaba dünya yazdırdık. 307 | 308 | Bu run methodu bazı işleri yapmak için yetersiz. Bu durumlarda [co](https://www.npmjs.com/package/co) kütüphanesi gibi kütüphaneler kullanabilirsiniz. Bu kütüphanelerin nasıl yazıldığıyla daha fazla bilginiz olsun diye yukardakinden daha gelişmiş bir [run methodu](run.md) yazdım. 309 | 310 | 311 | Fonksiyonu anlamak biraz güç olabilir ancak bu ve benzeri kütüphaneler aşağıdaki işleri yapabilirler. 312 | 313 | * Promise döndürerek bitişi takip etmek. 314 | 315 | ```ts 316 | run(generator()).then(function() { 317 | 318 | }); 319 | ``` 320 | 321 | * Promise hata verdiğinde ilgili satırda exception fırlatımı 322 | * Kod içerisinde başka bir generator fonksiyonu çağırabilirsiniz. örneğin; 323 | 324 | ```ts 325 | function* taskA() { 326 | console.log("taskA"); 327 | yield taskB(); 328 | console.log("taskA bitti"); 329 | } 330 | 331 | function* taskB() { 332 | console.log("taskB"); 333 | yield delay(1000); 334 | console.log("taskB bitti"); 335 | } 336 | 337 | run(taskA()).then(function() { 338 | console.log("tüm tasklar bitti"); 339 | }); 340 | ``` 341 | 342 | ```ts 343 | taskA 344 | taskB 345 | * 1 saniye bekler 346 | taskB bitti 347 | taskA bitti 348 | tüm tasklar bitti 349 | ``` 350 | 351 | * Dizi verilerek birden fazla taskın bitmesini bekleyebilirsiniz 352 | 353 | ```ts 354 | function* taskA() { 355 | yield delay(2000); 356 | console.log("taskA"); 357 | yield delay(1000); 358 | console.log("taskA bitti"); 359 | return "A"; 360 | } 361 | 362 | function* taskB() { 363 | yield delay(500); 364 | console.log("taskB"); 365 | yield delay(500); 366 | console.log("taskB bitti"); 367 | return "B"; 368 | } 369 | 370 | 371 | function* taskX() { 372 | console.log("taskX"); 373 | yield delay(1000); 374 | let arr = yield [taskA(), taskB()]; 375 | console.log(arr); 376 | yield delay(1000); 377 | console.log("taskX bitti"); 378 | } 379 | 380 | run(taskX()); 381 | ``` 382 | 383 | > **Ekleme:** `yield` keywordu eğer bir `*` yıldızla kullanılırsa sağında verilen nesnenin yield özelliklerini öğrenebilir. Örneğin; 384 | > 385 | > ```ts 386 | > function* say() { 387 | > yield 1; 388 | > yield 2; 389 | > } 390 | > 391 | > function* okubakim() { 392 | > yield* say(); 393 | > } 394 | > 395 | > let oku = okubakim(); 396 | > oku.next(); // {value: 1, done: false} 397 | > oku.next(); // {value: 2, done: false} 398 | > oku.next(); // {value: undefined, done: true} 399 | > ``` 400 | > 401 | > Ayrıca `yield*` dizi değerlerini tek tek verebilir ve string'in karakterlerini tek tek okuyabilir. 402 | > 403 | > ```ts 404 | > function* ayi() { 405 | > yield* "ayi"; 406 | > } 407 | > let neymis = ayi(); 408 | > neymis.next(); {value: 'a', done: false} 409 | > neymis.next(); {value: 'y', done: false} 410 | > neymis.next(); {value: 'i', done: false} 411 | > neymis.next(); {value: undefined, done: true} 412 | > ``` 413 | 414 | # ES7 - Async fonksiyonlar 415 | 416 | ES6'da asenkron işleri yönetmenin kolay yolunu gördük. Ancak yield'i direkt olarak fonksiyon çağrısı yaparak kullanamıyoruz. `run()` methoduna ihtiyacımız var. ES7'de bu ihtiyaç kaldırılarak `async function` syntax'ı gelmiştir. 417 | 418 | 419 | ```ts 420 | async function merhaba() { 421 | 422 | } 423 | ``` 424 | 425 | Yukardaki gibi asenkron fonksiyon tanımlaması yapabiliriz. 426 | 427 | ```ts 428 | async function merhaba() { 429 | console.log("merhaba"); 430 | } 431 | merhaba(); 432 | ``` 433 | 434 | ES6'da `run()` methodunu kullanmamız gerekirken, burada direkt olarak fonksiyon çağrısı yaptık. Asenkron fonksiyonları sanki promise yapısına çeviren bir keyword olarak düşünebiliriz. 435 | 436 | ```ts 437 | async function selam() { 438 | console.log("merhaba"); 439 | } 440 | 441 | // aşağıdaki gibi derleniyor 442 | 443 | function selam() { 444 | return new Promise(function(resolve, reject) { 445 | try { 446 | console.log("merhaba"); 447 | resolve(); 448 | } catch(e) { 449 | reject(e); 450 | } 451 | }); 452 | } 453 | 454 | // asenkron fonksiyon mutlaka bir promise döndürür. 455 | selam().then(function() { 456 | console.log("çalıştı"); 457 | }) 458 | ``` 459 | 460 | Peki bu async fonksiyonlarla neler yapabilirim. ES6'da `yield`'ı asenkron işleri bekletmek için kullanabiliyorduk. ES7'de bu iş için `await` keywordu bulunmaktadır. 461 | 462 | ```ts 463 | function delay(ms) { 464 | return new Promise(function(resolve) { 465 | setTimeout(resolve, ms) 466 | }); 467 | } 468 | 469 | async function selam() { 470 | console.log("merhaba"); 471 | await delay(1000); 472 | console.log("dünya"); 473 | } 474 | 475 | selam(); 476 | ``` 477 | 478 | async fonksiyonlar promise döndürdüğü için bir async fonksiyonu içinde await ile başka bir async fonksiyonu bekletebiliriz. 479 | 480 | ```ts 481 | async function taskA() { 482 | console.log("A"); 483 | let b = await taskB(); 484 | return "A" + b; 485 | } 486 | 487 | async function taskB() { 488 | console.log("B"); 489 | return "B"; 490 | } 491 | 492 | taskA().then(function(sonuc) { 493 | console.log(sonuc); // AB 494 | }) 495 | ``` 496 | 497 | Örnektede görüldüğü gibi await'i herhangi bir expression içinde kullanmamız mümkün. Bu sayede bir değişkene değer atayabiliriz. Döndürdüğümüz değer await'in bulunduğu konuma yerleştirilecektir. 498 | 499 | > **Önemli Not:** Bu tüm işlemler sırasında try catch kullanmanız şiddetle önerilir. İlerki Node.js sürümlerinde eğer promisede oluşmuş bir hata varsa ve catch ile yakalanmamışsa uygulama çökmüş gibi `process.exit()` işlemi yapılacaktır. 500 | 501 | > **Not:** `await` keywordune `null` verirsek bir hataya neden olmayacaktır. 502 | 503 | > **Not:** `await` keywordune promise harici bir değer verirsek direkt olarak pass edecektir yani `let t = await 1;` yaparsanız t değeri direkt 1 olacaktır. Normal fonksiyonları versenizde aynı şekide pass işlemi yapılacaktır. 504 | -------------------------------------------------------------------------------- /examples/1.js: -------------------------------------------------------------------------------- 1 | var async = require('../async.js'); 2 | var run = async.run; 3 | var delay = async.delay; 4 | var concurrent = async.concurrent; 5 | 6 | var d = Date.now(); 7 | 8 | function* taskA() { 9 | console.log("+A " + (Date.now() - d)); 10 | yield delay(500); 11 | console.log("-A " + (Date.now() - d)); 12 | return "A"; 13 | } 14 | 15 | function* taskB() { 16 | console.log("+B " + (Date.now() - d)); 17 | yield delay(300); 18 | console.log("-B " + (Date.now() - d)); 19 | return "B"; 20 | } 21 | 22 | function* taskC() { 23 | console.log("+C " + (Date.now() - d));1 24 | yield delay(250); 25 | console.log("-C " + (Date.now() - d)); 26 | return "C"; 27 | } 28 | 29 | function* main() { 30 | yield taskA(); 31 | let [a, b, c] = yield [taskA(), taskB(), taskC()]; 32 | console.log("all tasks ended", a, b, c); 33 | 34 | let [ca, cb, cc] = yield concurrent(2, [taskA(), taskB(), taskC()]); 35 | console.log("all tasks ended", ca, cb, cc); 36 | } 37 | 38 | run(main()).then(() => { 39 | console.log("program exit"); 40 | process.exit(0); 41 | }); -------------------------------------------------------------------------------- /examples/2.js: -------------------------------------------------------------------------------- 1 | var async = require('../async.js'); 2 | var run = async.run; 3 | var delay = async.delay; 4 | var concurrent = async.concurrent; 5 | 6 | var d = Date.now(); 7 | 8 | function* taskA() { 9 | console.log("1"); 10 | throw new Error(1); 11 | console.log("2"); 12 | } 13 | 14 | function* main() { 15 | console.log("3"); 16 | yield taskA(); 17 | console.log("4"); 18 | } 19 | 20 | run(main()).then(() => { 21 | console.log("program exit"); 22 | process.exit(0); 23 | },(err) => { 24 | console.log("program exit with error " + err.stack); 25 | process.exit(1); 26 | }); -------------------------------------------------------------------------------- /examples/3.js: -------------------------------------------------------------------------------- 1 | async function asenkron() { 2 | let hmm = await (async function() { 3 | return "hmm"; 4 | })(); 5 | 6 | console.log(hmm); 7 | return hmm; 8 | } 9 | 10 | asenkron().then((data) => { 11 | 12 | }).catch((err) => { 13 | 14 | }); 15 | 16 | -------------------------------------------------------------------------------- /examples/4.js: -------------------------------------------------------------------------------- 1 | async function taskA() { 2 | return taskB; // fonksiyon çağrısı yapılmadı 3 | } 4 | 5 | async function taskB() { 6 | return "1"; 7 | } 8 | 9 | async function main() { 10 | let deger = await await taskA(); // bu hatalı bir çağrı olur. 11 | // let deger = await (await taskA())(); olmalı 12 | 13 | console.log(deger); 14 | } 15 | 16 | main().then(function() { 17 | 18 | }); -------------------------------------------------------------------------------- /run.md: -------------------------------------------------------------------------------- 1 | ```ts 2 | function run(g) { 3 | return new Promise(function (resolve, reject) { // kendisi bir promise döndürüyor. 4 | (function innerRun(g, data) { 5 | var i = g.next(data); // bir sonraki değeri getir 6 | if (!i.done) { // eğer bitmediyse 7 | if (i.value != undefined && i.value.constructor == Promise) { // değer promise ise 8 | i.value.then(function (data) { // promise'in bitmesini bekle 9 | setTimeout(function (g) { // stack'in şişmesini engelliyoruz bu çağrıyla 10 | innerRun(g, data); // kendini çağır 11 | }, null, g); 12 | }).catch(function (e) { // promise hata döndürürse 13 | g.throw(e); // ilgili satırda hata fırlat 14 | }); 15 | } else if (i.value != undefined && i.value.toString() == "[object Generator]") { // eğer değer bir generatörse 16 | setTimeout(function (i, g) { // stack'in şişmesini engelle 17 | run(i.value).then(function (data) { // run çağrısı yap ve bittiğinde geri innerRun fonksiyonunu tetikle 18 | innerRun(g, data); 19 | }).catch(function (e) { 20 | g.throw(e); 21 | }); 22 | }, null, i, g); 23 | } else if (i.value != undefined && i.value.constructor == Array) { // eğer değer bir dizi ise 24 | setTimeout(function (i, g) { // stack'in şişmesini engelle 25 | var arr = i.value.map(function (t) { // her bir değer için 26 | if (t == null) { // null olmayanları 27 | return; 28 | } 29 | 30 | if (t.constructor == Promise) { // promise olanlar direkt olarak geçecek şekilde 31 | return t; 32 | } 33 | 34 | if (t.toString() == "[object Generator]") { // generatörler run'ı çağıracak biçimde 35 | return run(t); 36 | } 37 | }); 38 | Promise.all(arr).then(function (data) { // promise.all çağrısı yap bu sayede tüm bu promise'lerin bitmesini bekle 39 | innerRun(g, data); 40 | }).catch(function (e) { 41 | g.throw(e); 42 | }); 43 | }, null, i, g); 44 | } else { // eğer değer ne promise ne generatör nede diziyse 45 | setTimeout(function (i, g) { // stack'in şişmesini engelle 46 | innerRun(g, i.value); 47 | }, null, i, g); 48 | } 49 | } else { // done: true ise 50 | resolve(i.value); // bitir. 51 | } 52 | })(g); 53 | }); 54 | } 55 | ``` --------------------------------------------------------------------------------