JavaScript Vaatleri: giriş

Vaatler, ertelenmiş ve eşzamansız hesaplamaları basitleştirir. Vaat, henüz tamamlanmamış bir işlemi temsil eder.

Jake Archibald
Jake Archibald

Geliştiriciler, sektörün tarihindeki en önemli anlara hazır olun yardımcı oluyorum.

[Trabbi başlıyor]

JavaScript'te vaatler geldi.

[Havai fişekler patlıyor, yukarıdan parıldayan kağıt yağmur yağıyor, kalabalık çılgınca]

Bu aşamada, aşağıdaki kategorilerden birine girersiniz:

  • İnsanlar etrafınızda tezahürat yapıyor ama ne olup bittiğini bilmiyorsunuz konu. Belki de “vadetmek” konusunda bile emin olamıyorsun düşünülebilir. Omuzlarınızı silkiyorsunuz fakat omuzlarınızda ağırlık yükseliyor. Yanıtınız evet ise bunu neden önemsemem gerektiğini anlamam çok uzun zaman aldı. çok zorlandı. Bu işlemi en baştan yapmak isteyebilirsiniz.
  • Yumruklar havaya! Tam olarak vaktiniz var, değil mi? Bu Promise öğelerini daha önce kullandınız ancak tüm uygulamaların farklı API'lere sahip olması sizi rahatsız ediyor. Resmi JavaScript sürümüne ait API nedir? Proje yöneticisi olarak terminolojiyi kullanın.
  • Bunu zaten biliyordun ve hemen harekete geçenleri olduğunu düşünebilir. Bir dakikanızı ayırarak üstünlüğünüzü ortaya koyun, ardından doğrudan API referansına gidin.

Tarayıcı desteği ve çoklu dolgu

Tarayıcı Desteği

  • Chrome: 32..
  • Kenar: 12..
  • Firefox: 29..
  • Safari: 8..

Kaynak

Vaatlerin uygulanması konusunda eksiksiz bir özelliği sunmayan tarayıcıları spesifikasyona uygun hale getirmek için veya diğer tarayıcılara ve Node.js'ye vaatler ekleyerek, çoklu dolgu (2 k gzip ile sıkıştırılmış).

Peki ne dersiniz?

JavaScript tek iş parçacıklı olduğundan, iki bitlik komut dosyası aynı zamanda aynı zamanda; birbiri ardına yayınlaması gerekir. Tarayıcılarda, JavaScript tarayıcıdan farklı bir dizi başka şeyin olduğu bir ileti dizisi paylaştığında emin olun. Ancak genellikle JavaScript; boyama, güncelleme, ve kullanıcı işlemlerini (ör. metni vurgulama ve etkileşimi artırma) özelliğini kullanabilirsiniz. Bunlardan birindeki etkinlik diğerlerini geciktirir.

İnsan olarak çok iş parçacıklısınız. Birden çok parmağınızla yazabilirsiniz, bir konuşmayı aynı anda yürütebilirsiniz. Engelleyen tek şey Hapşırmayla uğraşmamız gereken, mevcut aktivitelerin askıya alınmamalıdır. Bu çok sinir bozucu, özellikle de sürüş sırasında ve konuşma yapmaya çalışırken. Hayır çok gizli bir kod yazmak istiyorsunuz.

Bu sorunu çözmek için büyük olasılıkla etkinlikleri ve geri çağırmaları kullandınız. Etkinlikler şunlardır:

var img1 = document.querySelector('.img-1');

img1.addEventListener('load', function() {
  // woo yey image loaded
});

img1.addEventListener('error', function() {
  // argh everything's broken
});

Bu hiç hapşırık değil. Resmi alıp birkaç dinleyici ekleriz ve JavaScript, bu işleyicilerden biri çağrılana kadar yürütülmeyi durdurabilir.

Yukarıdaki örnekte, maalesef bu olay yaşanmış olabilir. onları dinlemeye başlamadan önce, "tamamlanmış" resim özelliği:

var img1 = document.querySelector('.img-1');

function loaded() {
  // woo yey image loaded
}

if (img1.complete) {
  loaded();
}
else {
  img1.addEventListener('load', loaded);
}

img1.addEventListener('error', function() {
  // argh everything's broken
});

Burada, daha önce tespit edilen içerikleri dinleme fırsatı bulmadan önce hatalı görülen them; maalesef DOM bunun için bize bir yol sunmuyor. Ayrıca bu bir resim yükleniyor. İşler daha da karmaşık bir iştir: Bir grup kullanıcının resimden tanesi yüklendi.

Etkinlikler her zaman en iyi yöntem değildir

Etkinlikler, aynı anda birçok kez gerçekleşebilecek şeyler için idealdir nesne—keyup, touchstart vb. Bu etkinlikler sizi pek önemsemiyor ne olduğunu sorun. Ama yapay zeka, eş zamansız başarı/başarısızlık varsa şuna benzer bir şey istiyorsunuz:

img1.callThisIfLoadedOrWhenLoaded(function() {
  // loaded
}).orIfFailedCallThis(function() {
  // failed
});

// and…
whenAllTheseHaveLoaded([img1, img2]).callThis(function() {
  // all loaded
}).orIfSomeFailedCallThis(function() {
  // one or more failed
});

Vaat edilen şey bu, ama daha iyi adlandırma. HTML resim öğelerinde bir "hazır" bir vaat döndüren yöntem kullanıyorsa şu şekilde yapabiliriz:

img1.ready()
.then(function() {
  // loaded
}, function() {
  // failed
});

// and…
Promise.all([img1.ready(), img2.ready()])
.then(function() {
  // all loaded
}, function() {
  // one or more failed
});

En basit haliyle, vaatler etkinlik işleyicilere benzerdir, sadece şunlar hariç:

  • Bir vaat yalnızca bir kez başarılı ya da başarısız olabilir. İki kez başarılı ya da başarısız olamaz. ve tersi de geçerlidir.
  • Bir söz başarılı veya başarısız olduysa ve daha sonra başarı/başarısızlık eklerseniz geri çağırması için, etkinliğin daha erken gidin.

Bu, eş zamansız başarı/başarısızlık için çok yararlıdır çünkü bir şeyin tam olarak ne zaman kullanılabilir olduğunu öğrenmekle ve daha fazla karar vermeye yardımcı olur.

Vaat terminolojisi

Domenic Denicola kanıtı ilk taslağı okudu ve bana "F" olarak not verdi bir ad da verebilirsiniz. Beni gözaltına aldı, beni kopyalamaya zorladı Eyaletler ve Kadimler 100 defa çaldım ve anne ve babama kaygılı bir mektup yazdım. Buna rağmen birçok terminolojiyi karıştırabiliriz, ancak temel bilgiler şunlardır:

Sözler şöyle olabilir:

  • Yerine getirildi - Vaat ile ilgili işlem başarılı oldu
  • rejected (reddedildi): Vaatle ilgili işlem başarısız oldu
  • beklemede: Henüz yerine getirmedi veya reddetmedi
  • settled: Karşılandı veya reddedildi

Spesifikasyon aynı zamanda, vaat benzeri, işlev içeren bir nesneyi tanımlamak için thenable terimini kullanır. then yöntemini kullanması gerekir. Bu terim bana eski İngiltere futbolunu hatırlatıyor Terry Venables Yöneticisi Mümkün olduğunca az kullanacağım.

Vaatler JavaScript'e gelir!

Vaatler bir süredir kütüphane biçiminde kullanılıyor. Örneğin:

Yukarıdakiler ve JavaScript vaatleri ortak, standartlaştırılmış bir davranışı Promises/A+ olarak adlandırılır. Eğer bir jQuery kullanıcısıysanız, onda da Ertelenenler. Ancak, Ertelenenler, Promise/A+ ile uyumlu olmadığından biraz farklı ve daha az faydalı bu yüzden dikkatli olun. jQuery, Taahhüt türü ancak bu yalnızca ve aynı sorunlara sahip olduğunu görebilirsiniz.

Vaat uygulamaları standartlaştırılmış bir davranış izler ancak API'ler genel olarak birbirinden farklıdır. JavaScript vaatleri, API'deki LCV.js ile benzerdir. Vaat oluşturmak için şu adımları uygulayın:

var promise = new Promise(function(resolve, reject) {
  // do a thing, possibly async, then…

  if (/* everything turned out fine */) {
    resolve("Stuff worked!");
  }
  else {
    reject(Error("It broke"));
  }
});

Vaat oluşturucu tek bir bağımsız değişken, bir geri çağırma ve iki parametreli çözüm bulmanız ve reddetmeniz gerekir. Geri çağırma sırasında eşzamansız gibi bir işlem yapın, ardından şunu arayın: her şey işe yaradıysa çözümleyin, aksi takdirde ret çağrısı yapın.

Düz eski JavaScript'teki throw gibi, bu da geleneksel olarak kullanılır, ancak zorunlu değildir. hata nesnesiyle reddedilir. Hata nesnelerinin avantajı, bir hatanın hata ayıklama araçlarını daha faydalı hale getirir.

İşte bu sözü nasıl kullanacağınız:

promise.then(function(result) {
  console.log(result); // "Stuff worked!"
}, function(err) {
  console.log(err); // Error: "It broke"
});

then(), başarılı bir durum için geri çağırma olmak üzere iki bağımsız değişken ve başka bir bağımsız değişken alır. belirtin. Her ikisi de isteğe bağlıdır. Böylece, destekleyici olması gerekir.

JavaScript'in DOM'de "Vadeli" olarak başladığı, "Promises" olarak yeniden adlandırıldığı ve son olarak JavaScript'e geçtim. Bu bilgilerin DOM mükemmeldir çünkü bunlar, Node.js (başka bir soru da bunları temel API'lerinde kullanıp kullanmadığıdır).

Bunlar bir JavaScript özelliği olsa da DOM, bunları kullanmaktan korkmaz. İçinde Aslında, eş zamansız başarı/başarısızlık yöntemlerine sahip tüm yeni DOM API'leri, vaat edilenleri kullanır. Bu halihazırda proje yönetimi Kota Yönetimi, Yazı Tipi Yükleme Etkinlikleri ServiceWorker, Web MIDI Akışlar ve daha fazlası.

Diğer kitaplıklarla uyumluluk

JavaScript, API'nin then() yöntemi olan her şeyi aşağıdaki gibi ele alacağını taahhüt ediyor: sözlü (veya sözlü konuşma içerisinde thenable) bir kitaplık kullanıyorsanız. sorun değil, yeni klavye stiliyle uyumlu bir oyun JavaScript vaat ediyor.

Bahsettiğim gibi, jQuery'nin Ertelenenler özelliği pek yardımcı olmadı. Neyse ki bunları standart vaatlere uygulayabilirsiniz. en kısa sürede tamamlamanızı öneririz:

var jsPromise = Promise.resolve($.ajax('/whatever.json'))

Burada, jQuery'nin $.ajax işlevi Ertelenenli değerini döndürür. then() yöntemi olduğu için Promise.resolve() bunu bir JavaScript taahhüdüne dönüştürebilir. Ancak, Bazen ertelenenler, geri çağırma işlemlerine birden fazla bağımsız değişken iletir. Örneğin:

var jqDeferred = $.ajax('/whatever.json');

jqDeferred.then(function(response, statusText, xhrObj) {
  // ...
}, function(xhrObj, textStatus, err) {
  // ...
})

Oysa JS, ilki hariç hepsinin yoksayılacağını vaat eder:

jsPromise.then(function(response) {
  // ...
}, function(xhrObj) {
  // ...
})

Neyse ki genellikle istediğiniz şey budur ya da en azından istediğinize karar verin. Ayrıca, jQuery'nin retlere hata nesneleri geçirme.

Karmaşık eşzamansız kod artık daha kolay

Peki, şimdi bir şeyler kodlayalım. Şunları yapmak istediğimizi varsayalım:

  1. Yükleme durumunu göstermek için döner simgeyi başlatın
  2. Hikaye için bir JSON dosyası alın. Bu şekilde her bölümün başlığı ve URL'leri yer alır.
  3. Sayfaya başlık ekleyin
  4. Her bölümü getir
  5. Hikayeyi sayfaya ekleyin
  6. Döner simgeyi durdur

... ancak aynı zamanda kullanıcıya işlem sırasında bir sorun olup olmadığını da bildirir. Şunları istiyoruz: o noktada döner simgeyi durduracak, aksi takdirde dönmeye devam edecektir. başka bir kullanıcı arayüzüyle karşılaşabilirsiniz.

Elbette bir hikayeyi iletmek için JavaScript'i daha hızlıdır, Ancak bu model API'lerle çalışırken oldukça yaygındır: Birden fazla veri ve işlem tamamlandığında bir işlem yapılacaklar.

İlk olarak ağdan veri getirme konusunu ele alalım:

XMLHttpRequest ile ilgili söz verme

Geriye dönük olarak mümkünse eski API'ler vaatleri kullanacak şekilde güncellenecek uyumlu bir şekilde kullanabilirsiniz. XMLHttpRequest, birincil aday olsa da GET isteği yapmak için basit bir fonksiyon yazalım:

function get(url) {
  // Return a new promise.
  return new Promise(function(resolve, reject) {
    // Do the usual XHR stuff
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      // This is called even on 404 etc
      // so check the status
      if (req.status == 200) {
        // Resolve the promise with the response text
        resolve(req.response);
      }
      else {
        // Otherwise reject with the status text
        // which will hopefully be a meaningful error
        reject(Error(req.statusText));
      }
    };

    // Handle network errors
    req.onerror = function() {
      reject(Error("Network Error"));
    };

    // Make the request
    req.send();
  });
}

Şimdi bunu kullanalım:

get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.error("Failed!", error);
})

Artık manuel olarak XMLHttpRequest yazmadan HTTP istekleri yapabiliyoruz. Bu çok iyi, çünkü XMLHttpRequest develerinin korkutucu hâlini görmem azsa hayatım daha mutlu olacak.

Zincirleme

then(), hikayenin sonu değil. then öğelerini birlikte zincir halinde bağlayabilirsiniz. değerleri dönüştürme veya ek eşzamansız işlemleri art arda çalıştırma.

Değerleri dönüştürme

Değerleri, yeni değeri döndürerek kolayca dönüştürebilirsiniz:

var promise = new Promise(function(resolve, reject) {
  resolve(1);
});

promise.then(function(val) {
  console.log(val); // 1
  return val + 2;
}).then(function(val) {
  console.log(val); // 3
})

Pratik bir örnek olarak, şuna geri dönelim:

get('story.json').then(function(response) {
  console.log("Success!", response);
})

Yanıt JSON biçimindedir ancak şu anda düz metin olarak alıyoruz. Biz JSON öğesini kullanmak için get işlevimizi değiştirebilir responseType ama gelecek vaatlerde de bu sorunu çözebiliriz:

get('story.json').then(function(response) {
  return JSON.parse(response);
}).then(function(response) {
  console.log("Yey JSON!", response);
})

JSON.parse() tek bir bağımsız değişken alıp dönüştürülmüş bir değer döndürdüğünden, şu şekilde bir kısayol oluşturabiliriz:

get('story.json').then(JSON.parse).then(function(response) {
  console.log("Yey JSON!", response);
})

Hatta getJSON() işlevini oldukça kolay hale getirebiliriz:

function getJSON(url) {
  return get(url).then(JSON.parse);
}

getJSON(), yine de bir URL getiren ve ardından ayrıştıran bir taahhüt döndürüyor JSON biçiminde olacaktır.

Eşzamansız işlemleri sıraya ekleme

Ayrıca eşzamansız işlemleri sırayla çalıştırmak için then zincirleri oluşturabilirsiniz.

then() geri aramasından bir şey döndürdüğünüzde kendinizi sihirli bir şekilde hissedersiniz. Bir değer döndürürseniz sonraki then() bu değerle çağrılır. Ancak, Bir vadettiğinizde geri dönerseniz, sonraki then() bunu bekler ve yalnızca söz konusu vaat yerine geldiğinde (başarılı/başarısız) çağrılır. Örneğin:

getJSON('story.json').then(function(story) {
  return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
  console.log("Got chapter 1!", chapter1);
})

Burada, story.json için eşzamansız bir istek gerçekleştiriyoruz. Bu istek bize URL'leri göndermeniz gerekiyorsa bunlardan ilkini isteriz. İşte tam bu noktada basit geri çağırma kalıplarından öne çıkmaya başlayabilir.

Bölümleri almak için kısayol da oluşturabilirsiniz:

var storyPromise;

function getChapter(i) {
  storyPromise = storyPromise || getJSON('story.json');

  return storyPromise.then(function(story) {
    return getJSON(story.chapterUrls[i]);
  })
}

// and using it is simple:
getChapter(0).then(function(chapter) {
  console.log(chapter);
  return getChapter(1);
}).then(function(chapter) {
  console.log(chapter);
})

getChapter çağrılana kadar story.json dosyasını indirmeyiz, ancak sonraki getChapter olarak adlandırdığımız zaman, hikayede sunduğumuz sözü yeniden kullanıyoruz. Bu nedenle story.json yalnızca bir kez getirilir. Yaşasın!

Hata işleme

Daha önce gördüğümüz gibi, then(), biri başarı, diğeri başarı için olmak üzere iki bağımsız değişken alır başarısızlık (veya vaatlerde bulunma, yerine getirme ve reddetme):

get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.log("Failed!", error);
})

catch() uzantısını da kullanabilirsiniz:

get('story.json').then(function(response) {
  console.log("Success!", response);
}).catch(function(error) {
  console.log("Failed!", error);
})

catch() ile ilgili özel bir şey yok, yalnızca şekerdir then(undefined, func) ama daha okunabilir. Bu iki kod yukarıdaki örnekler aynı şekilde davranmaz, ikinci durum şuna eşdeğerdir:

get('story.json').then(function(response) {
  console.log("Success!", response);
}).then(undefined, function(error) {
  console.log("Failed!", error);
})

Aralarındaki fark küçük ama son derece yararlıdır. Reddetme vaadini atla bir ret geri çağırması ile sonraki then() adresine yönlendirin (veya catch() eşdeğerdir). then(func1, func2) ile func1 veya func2 olacak değil, her ikisi birden çağrılmaz. Ancak then(func1).catch(func2) ile her ikisi de Bunlar zincirde ayrı adımlar olduğundan func1 reddederse çağrılır. Şu hattı kullanın: şu:

asyncThing1().then(function() {
  return asyncThing2();
}).then(function() {
  return asyncThing3();
}).catch(function(err) {
  return asyncRecovery1();
}).then(function() {
  return asyncThing4();
}, function(err) {
  return asyncRecovery2();
}).catch(function(err) {
  console.log("Don't worry about it");
}).then(function() {
  console.log("All done!");
})

Yukarıdaki akış, normal JavaScript dene/yakalama işlemine çok benzer; bir "deneme" içinde gerçekleşir hemen catch() blokuna gidin. İşte tıklayın (çünkü akış şemalarını seviyorum):

Yerine gelen vaatler için mavi çizgileri, yerine getiren vaatler için kırmızı çizgileri takip edin. reddet.

JavaScript istisnaları ve taahhütleri

Retler, bir vaat açık bir şekilde reddedildiğinde ancak dolaylı olarak reddedildiğinde oluşturucu geri çağırmasında bir hata atılırsa:

var jsonPromise = new Promise(function(resolve, reject) {
  // JSON.parse throws an error if you feed it some
  // invalid JSON, so this implicitly rejects:
  resolve(JSON.parse("This ain't JSON"));
});

jsonPromise.then(function(data) {
  // This never happens:
  console.log("It worked!", data);
}).catch(function(err) {
  // Instead, this happens:
  console.log("It failed!", err);
})

Yani vaatlerinizle ilgili tüm çalışmalarınızı söz konusu oluşturucu geri çağırması için, hatalar otomatik olarak yakalanır ve reddedilenlere dönüşür.

Aynı durum, then() geri çağırma işlevinde bildirilen hatalar için de geçerlidir.

get('/').then(JSON.parse).then(function() {
  // This never happens, '/' is an HTML page, not JSON
  // so JSON.parse throws
  console.log("It worked!", data);
}).catch(function(err) {
  // Instead, this happens:
  console.log("It failed!", err);
})

Uygulamada hata işleme

Hikayemizde ve bölümlerimizde kullanıcıya bir hatayı göstermek için yakalamayı kullanabiliriz:

getJSON('story.json').then(function(story) {
  return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
  addHtmlToPage(chapter1.html);
}).catch(function() {
  addTextToPage("Failed to show chapter");
}).then(function() {
  document.querySelector('.spinner').style.display = 'none';
})

story.chapterUrls[0] getirme işlemi başarısız olursa (ör. http 500 veya kullanıcı çevrimdışıysa) başarılı geri çağırmaların tümünü atlar. Bunlar, getJSON(), yanıtı JSON olarak ayrıştırmaya çalışır ve geri çağırmayı içerir. Bunun yerine, geri arama. Sonuç olarak, "Bölüm gösterilemedi" eklemesi için önceki işlemlerin hiçbiri başarısız oldu.

JavaScript'in deneme-yakalama yönteminde olduğu gibi, hata yakalanır ve sonraki kod devam ettiğinden döner simge her zaman gizli kalır ve bizim istediğimiz de budur. İlgili içeriği oluşturmak için kullanılan yukarıdakilerin engelleyici olmayan eş zamansız sürümü haline gelir:

try {
  var story = getJSONSync('story.json');
  var chapter1 = getJSONSync(story.chapterUrls[0]);
  addHtmlToPage(chapter1.html);
}
catch (e) {
  addTextToPage("Failed to show chapter");
}
document.querySelector('.spinner').style.display = 'none'

Kurtarma işlemi yapmadan, yalnızca günlük kaydı amacıyla catch() isteyebilirsiniz. kaldıracak. Bunu yapmak için hatayı yeniden atın. Bunu aynı zamanda getJSON() yöntemimiz:

function getJSON(url) {
  return get(url).then(JSON.parse).catch(function(err) {
    console.log("getJSON failed for", url, err);
    throw err;
  });
}

Bir bölümü getirmeyi başardık ama hepsini istiyoruz. Haydi, kaç kişiye ihtiyacınız var demektir.

Paralellik ve sıralama: Her ikisinden de en iyi şekilde yararlanmak

Eş zamansız düşünmek kolay değildir. Hedeften çıkmakta zorlanıyorsanız kodu eş zamanlıymış gibi yazmayı deneyin. Bu durumda:

try {
  var story = getJSONSync('story.json');
  addHtmlToPage(story.heading);

  story.chapterUrls.forEach(function(chapterUrl) {
    var chapter = getJSONSync(chapterUrl);
    addHtmlToPage(chapter.html);
  });

  addTextToPage("All done");
}
catch (err) {
  addTextToPage("Argh, broken: " + err.message);
}

document.querySelector('.spinner').style.display = 'none'

Böyle olur. Ancak senkronizasyon çalışır ve öğeler indirilirken tarayıcı kilitlenir. Alıcı: bu işi eşzamansız hale getirmek için then() kullanıyoruz.

getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);

  // TODO: for each url in story.chapterUrls, fetch & display
}).then(function() {
  // And we're all done!
  addTextToPage("All done");
}).catch(function(err) {
  // Catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  // Always hide the spinner
  document.querySelector('.spinner').style.display = 'none';
})

Peki, bölüm url'lerini nasıl dolaşabilir ve onları nasıl sıraya alabiliriz? Bu çalışmaz:

story.chapterUrls.forEach(function(chapterUrl) {
  // Fetch chapter
  getJSON(chapterUrl).then(function(chapter) {
    // and add it to the page
    addHtmlToPage(chapter.html);
  });
})

forEach eş zamansız olarak uyumlu olmadığından bölümlerimiz istediğiniz sırada gösterilir indiriyorlar. Ucuz Kurgu'nun da bu şekilde yazıldığını fark ettik. Bu değil Ucuz Kurgu, bu sorunu düzeltelim.

Dizi oluşturma

chapterUrls dizimizi bir vaatlere dönüştürmek istiyoruz. Bu işlemi then() kullanarak yapabiliriz:

// Start off with a promise that always resolves
var sequence = Promise.resolve();

// Loop through our chapter urls
story.chapterUrls.forEach(function(chapterUrl) {
  // Add these actions to the end of the sequence
  sequence = sequence.then(function() {
    return getJSON(chapterUrl);
  }).then(function(chapter) {
    addHtmlToPage(chapter.html);
  });
})

Promise.resolve()'ı ilk kez gördük. Bu da, sizi bir sonuca ulaştıracak bir vaat demektir. Sınavı geçerseniz Promise örneği ise onu döndürür (not: bu bir bazı uygulamaların henüz uymadığı spesifikasyonlarda değişiklik yapabilirsiniz). Şu durumda: söz gibi bir şey ilettiğinde (then() yöntemi vardır), aynı şekilde karşılayan/reddeden orijinal Promise. Sınavı geçerseniz başka herhangi bir değerde, ör. Promise.resolve('Hello') ise sağlayacak bir vaatte bulunmayı ifade eder. Parametreye değer içermeyen bir şekilde Yukarıdaki gibi, "undefined" ile karşılanır.

Bir de Promise.reject(val) var. Bu da reddeden bir vaat ona verdiğiniz (veya tanımlanmamış) değer.

Yukarıdaki kodu sadeleştirmek için array.reduce:

// Loop through our chapter urls
story.chapterUrls.reduce(function(sequence, chapterUrl) {
  // Add these actions to the end of the sequence
  return sequence.then(function() {
    return getJSON(chapterUrl);
  }).then(function(chapter) {
    addHtmlToPage(chapter.html);
  });
}, Promise.resolve())

Bu, önceki örnekle aynı şeyi yapar, ancak "dizi" değişkenine eklenmelidir. Dizideki her öğe için azaltılmış geri çağırma yöntemimiz çağrılır. "dizi" ilk sefer Promise.resolve(), ancak "dizi" komutunu çağırır önceki görüşmede verdiğimiz yanıttır. array.reduce. bir diziyi tek bir değere indirmek için çok kullanışlıdır. Bu örnekte, bir vaattir.

Hepsini bir araya getirelim:

getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);

  return story.chapterUrls.reduce(function(sequence, chapterUrl) {
    // Once the last chapter's promise is done…
    return sequence.then(function() {
      // …fetch the next chapter
      return getJSON(chapterUrl);
    }).then(function(chapter) {
      // and add it to the page
      addHtmlToPage(chapter.html);
    });
  }, Promise.resolve());
}).then(function() {
  // And we're all done!
  addTextToPage("All done");
}).catch(function(err) {
  // Catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  // Always hide the spinner
  document.querySelector('.spinner').style.display = 'none';
})

Senkronizasyon sürümünün tamamen eş zamansız bir versiyonunu ele aldık. Ama yapabiliriz daha iyi hale getirir. Şu anda sayfamız şu şekilde indiriliyor:

Tarayıcılar aynı anda birden fazla şey indirme konusunda oldukça başarılı olduğundan indirdikten sonra bölümleri pratiğe dökebilirsiniz. Bizim hedefimiz aynı anda indirmek ve tümü geldiğinde işleyin. Neyse ki bunun için bir API var:

Promise.all(arrayOfPromises).then(function(arrayOfResults) {
  //...
})

Promise.all, bir dizi vaatte bulunur ve bunları gerçekleştiren bir vaat oluşturur ve tüm görevleri başarıyla tamamlandığından oluşur. Çeşitli sonuçlar (isterseniz vaatleri yerine getirmek için) ve bunu verdiğiniz vaatlerle aynı sırayla gösterin.

getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);

  // Take an array of promises and wait on them all
  return Promise.all(
    // Map our array of chapter urls to
    // an array of chapter json promises
    story.chapterUrls.map(getJSON)
  );
}).then(function(chapters) {
  // Now we have the chapters jsons in order! Loop through…
  chapters.forEach(function(chapter) {
    // …and add to the page
    addHtmlToPage(chapter.html);
  });
  addTextToPage("All done");
}).catch(function(err) {
  // catch any error that happened so far
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  document.querySelector('.spinner').style.display = 'none';
})

Bağlantıya bağlı olarak, bu işlem, tek tek yüklemeye kıyasla saniyeler daha hızlı olabilir. ve ilk denememizden daha az kod olmasını sağladık. Bölümler istediğiniz dilde indirilebilir ancak ekranda doğru sırada görünüyorlar.

Bununla birlikte, algılanan performansı iyileştirebiliriz. Birinci bölüm geldiğinde sayfaya eklemesi gerekir. Böylece kullanıcı diğer kısımlardan önce okumaya başlayabilir ve bölümler geldi. Üçüncü bölüm geldiğinde, bunu sayfa, kullanıcı ikinci bölümün eksik olduğunu fark etmeyebilir. İkinci bölüm geldiğinde ikinci ve üçüncü bölümleri ekleyebiliriz.

Bunu yapmak için tüm bölümlerimiz için JSON'yi aynı anda getiriyoruz ve ardından adım adım sırayı izleyin:

getJSON('story.json')
.then(function(story) {
  addHtmlToPage(story.heading);

  // Map our array of chapter urls to
  // an array of chapter json promises.
  // This makes sure they all download in parallel.
  return story.chapterUrls.map(getJSON)
    .reduce(function(sequence, chapterPromise) {
      // Use reduce to chain the promises together,
      // adding content to the page for each chapter
      return sequence
      .then(function() {
        // Wait for everything in the sequence so far,
        // then wait for this chapter to arrive.
        return chapterPromise;
      }).then(function(chapter) {
        addHtmlToPage(chapter.html);
      });
    }, Promise.resolve());
}).then(function() {
  addTextToPage("All done");
}).catch(function(err) {
  // catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  document.querySelector('.spinner').style.display = 'none';
})

Böylece her ikisinin de en iyisini yapabilirsiniz. Teslim etmek de aynı sürede kullanıcı, içeriğin ilk kısmını daha erken alır.

Bu basit örnekte, tüm bölümler yaklaşık olarak aynı zamanda geliyor, ancak teker teker teker teker göstermenin avantajı, daha fazla ve daha büyük bölümler.

Yukarıdakileri, Node.js stili geri çağırma işlevleriyle veya etkinlikler ancak daha önemlisi takip edilmesi kolay değildir. Ancak bu diğer ES6 özellikleriyle birleştirildiğinde vaatler için hikayenin sonu değil. işleri daha da kolay hale getirir.

Bonus turu: genişletilmiş özellikler

Bu makaleyi ilk yazdığımdan bu yana Promises'i kullanma imkanım değişti. çok teşekkür ederim. Chrome 55'ten bu yana, eş zamansız işlevler vaat temelli kodun eş zamanlıymış gibi (ana iş parçacığını engellemeden) yazılmıştır. Şunları yapabilirsiniz: Eşzamansız işlevler makalemde bu konuda daha fazla bilgi bulabilirsiniz. Evet, Belli başlı tarayıcılarda hem Promises hem de eşzamansız işlevler için yaygın destek sunulmaktadır. Ayrıntıları MDN'de Vadet ve async işlev bir referans noktası olarak kabul edilir.

Anne van Kesteren, Domenic Denicola, Tom Ashworth, Remy Sharp, Bunu gözden geçiren Addy Osmani, Arthur Evans ve Yutaka Hirano düzeltmeler/öneriler.

Ayrıca, Mathias Bynens'e farklı kısımlarında güncelleme inceleyin.