Eş zamansız işlevler: Vaatleri anlaşılır hale getirme

Eşzamansız işlevler, eş zamanlıymış gibi taahhüt tabanlı kodlar yazmanıza olanak tanır.

Jake Archibald
Jake Archibald

Eş zamansız işlevler Chrome, Edge, Firefox ve Safari'de varsayılan olarak etkindir ve gerçekten harikalar. Bunlar, vaadi temelli kod yazmanıza ana iş parçacığını engellemeden e-posta listesinden çıkabilirsiniz. Bunlar, eşzamansız kod daha az "zever" ve daha okunabilir olmasını sağlayın.

Eş zamansız işlevler şu şekilde çalışır:

async function myFirstAsyncFunction() {
  try {
    const fulfilledValue = await promise;
  } catch (rejectedValue) {
    // …
  }
}

İşlev tanımından önce async anahtar kelimesini kullanırsanız şunu kullanabilirsiniz: await değerini döndürür. Bir vaadi await yaptığınızda işlev duraklatılır. şekilde engellenmeyecek. Vaat yerine gelirse değeri geri almaktır. Vaat reddedilirse reddedilen değer atılır.

Tarayıcı desteği

Tarayıcı Desteği

  • Chrome: 55..
  • Kenar: 15..
  • Firefox: 52..
  • Safari: 10.1.

Kaynak

Örnek: bir getirme işlemini günlüğe kaydetme

Bir URL getirmek istediğinizi ve yanıtı metin olarak günlüğe kaydetmek istediğinizi düşünelim. Nasıl görünür? vaatleri kullanarak:

function logFetch(url) {
  return fetch(url)
    .then((response) => response.text())
    .then((text) => {
      console.log(text);
    })
    .catch((err) => {
      console.error('fetch failed', err);
    });
}

Burada da aynı şeyi eşzamansız işlevlerin kullanıldığı açıklayalım:

async function logFetch(url) {
  try {
    const response = await fetch(url);
    console.log(await response.text());
  } catch (err) {
    console.log('fetch failed', err);
  }
}

Aynı sayıda satır var ancak tüm geri çağırmalar kayboldu. Böylece, özellikle de vaatlere aşina olmayanlar için daha kolay okunuyor.

Eş zamansız döndürülen değerler

await kullansanız da kullanmasanız da eşzamansız işlevler her zaman belirli bir değer döndürür. O işlevi, eş zamansız işlevin döndürdüğü şeyle çözümlenir veya eşzamansız işlevin attığı her şeyi. Dolayısıyla:

// wait ms milliseconds
function wait(ms) {
  return new Promise((r) => setTimeout(r, ms));
}

async function hello() {
  await wait(500);
  return 'world';
}

...hello() çağrısının yapılması, "world" ile gerçekleştirileceği bir sözü döndürür.

async function foo() {
  await wait(500);
  throw Error('bar');
}

...foo() çağrısı yapıldığında Error('bar') ile beraber reddeden bir söz döndürülür.

Örnek: yanıt akışı sağlama

Daha karmaşık örneklerde eşzamansız işlevlerin avantajı daha da artar. İstediğinizi söyleyin (parçalardan çıkış yaparken yanıt akışı gerçekleştirebilir ve son boyutu döndürebilirsiniz).

Sözlerle şöyle:

function getResponseSize(url) {
  return fetch(url).then((response) => {
    const reader = response.body.getReader();
    let total = 0;

    return reader.read().then(function processResult(result) {
      if (result.done) return total;

      const value = result.value;
      total += value.length;
      console.log('Received chunk', value);

      return reader.read().then(processResult);
    });
  });
}

Şuna bir göz atın: Jake "vaatlerin sahibi" Archibald. Nasıl aradığımı göster processResult() kendi içinde eşzamansız bir döngü oluşturmak için nasıl kullanılır? İyi bir yazı kendimi çok akıllı hissediyorum. Ancak çoğu "akıllı" bir bakmışsınız, ona bakmanız gerekiyor. ne yaptığını anlamak için birkaç yaşını doldurdu. 1990'lar.

Bunu eşzamansız işlevlerle tekrar deneyelim:

async function getResponseSize(url) {
  const response = await fetch(url);
  const reader = response.body.getReader();
  let result = await reader.read();
  let total = 0;

  while (!result.done) {
    const value = result.value;
    total += value.length;
    console.log('Received chunk', value);
    // get the next result
    result = await reader.read();
  }

  return total;
}

Tüm "akıllı" kayboldu. Kendimi bu kadar kötü hissettiren eş zamansız döngü güven dolu, sıkıcı ve süreç döngülerinden ibarettir. şimdi daha iyi oldu. Gelecekte, eş zamansız yinelemeler hangi while döngüsünü bir döngüyle değiştirerek daha düzenli hale getirin.

Diğer eşzamansız işlevin söz dizimi

Size daha önce async function() {} anahtar kelimesini gösterdim ancak async anahtar kelimesi diğer işlev söz dizimiyle kullanılır:

Ok işlevleri

// map some URLs to json-promises
const jsonPromises = urls.map(async (url) => {
  const response = await fetch(url);
  return response.json();
});

Nesne yöntemleri

const storage = {
  async getAvatar(name) {
    const cache = await caches.open('avatars');
    return cache.match(`/avatars/${name}.jpg`);
  }
};

storage.getAvatar('jaffathecake').then();

Sınıf yöntemleri

class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jaffathecake').then();

Dikkatli ol! Çok sıralı ifadeler kullanmaktan kaçının

Eşzamanlı görünen kod yazsanız da aynı kodu atlamadığınızdan emin olun paralel olarak yapmanız gereken şeylerdir.

async function series() {
  await wait(500); // Wait 500ms…
  await wait(500); // …then wait another 500ms.
  return 'done!';
}

Yukarıdaki işlemin tamamlanması 1.000 ms sürer ancak:

async function parallel() {
  const wait1 = wait(500); // Start a 500ms timer asynchronously…
  const wait2 = wait(500); // …meaning this timer happens in parallel.
  await Promise.all([wait1, wait2]); // Wait for both timers in parallel.
  return 'done!';
}

Her iki bekleme işlemi aynı anda gerçekleştiğinden yukarıdaki işlemin tamamlanması 500 ms sürer. Pratik bir örneğe göz atalım.

Örnek: Getirme işlemlerini sırasıyla çıkarma

Diyelim ki bir dizi URL getiriyorsunuz ve bunları mümkün olan en kısa sürede emin olabilirsiniz.

Derin nefes - Vaat edilen sözler şu şekilde görünür:

function markHandled(promise) {
  promise.catch(() => {});
  return promise;
}

function logInOrder(urls) {
  // fetch all the URLs
  const textPromises = urls.map((url) => {
    return markHandled(fetch(url).then((response) => response.text()));
  });

  // log them in order
  return textPromises.reduce((chain, textPromise) => {
    return chain.then(() => textPromise).then((text) => console.log(text));
  }, Promise.resolve());
}

Doğru duydunuz, bir dizi vaat için reduce kullanıyorum. Çok akıllı. Ancak bu kod biraz çok akıllı olduğu için yazmadan daha iyi olacak.

Ancak, yukarıdakileri eş zamansız bir fonksiyona dönüştürürken çok sıralı:

Önerilmez - çok sıralı
async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
Çok daha düzenli görünüyor ancak ilk getirme işlemim tamamlanana kadar ikinci getirme işlemim başlamıyor okunduğunu görebilirsiniz. Bu, vaat edilen örneğine göre çok daha yavaş. paralel olarak eşlemenizi sağlar. Neyse ki ideal bir orta yol var.
Önerilen: güzel ve paralel
function markHandled(...promises) {
  Promise.allSettled(promises);
}

async function logInOrder(urls) {
  // fetch all the URLs in parallel
  const textPromises = urls.map(async (url) => {
    const response = await fetch(url);
    return response.text();
  });

  markHandled(...textPromises);

  // log them in sequence
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}
Bu örnekte, URL'ler paralel olarak getirilir ve okunur, ancak "akıllı" reduce bit yerine standart, sıkıcı, okunabilir bir döngü kullanılacak.
ziyaret edin.

Tarayıcı desteği geçici çözümü: oluşturucular

Oluşturma araçları (örneğin, Tüm önemli tarayıcıların en yeni sürümü ) çoklu dolgu eşzamansız işlevleri sıralayabilirsiniz.

Babel bunu sizin için yapar, bu örnekte Babel REPL üzerinden bir örnek verilmiştir

ziyaret edin.

Dönüştürme yaklaşımını kullanmanızı öneririm çünkü bu yaklaşımı hedef tarayıcılar eşzamansız işlevleri destekler, ancak bununla birlikte gerçekten bir aktarıyorsanız, Babel'ın çoklu dolgusu kendiniz kullanabilirsiniz. Şunun yerine:

async function slowEcho(val) {
  await wait(1000);
  return val;
}

...çoklu dolguyu dahil edersiniz ve şunu yazın:

const slowEcho = createAsyncFunction(function* (val) {
  yield wait(1000);
  return val;
});

createAsyncFunction adlı iş ortağına bir oluşturucu (function*) iletmeniz gerektiğini unutmayın. ve await yerine yield kullanın. Bunun dışında aynı şekilde çalışır.

Geçici çözüm: yeniden oluşturucu

Eski tarayıcıları hedefliyorsanız, Babel ayrıca oluşturucuları, IE8'e kadar eş zamansız işlevleri kullanmanıza olanak tanır. Bunun için ihtiyacınız olanlar Babel'ın es2017 hazır ayarı ve es2015 hazır ayarı.

Çıktı pek hoş değil, bu yüzden çok iyi olur.

Her şeyi eş zamansız olarak yapın.

Eş zamansız işlevler tüm tarayıcılarda görüntülendiğinde, vaat veren işlev! Sadece kodunuzu daha düzenli hale getirmekle kalmaz, aynı zamanda işlevin her zaman bir vaat döndürdüğünden emin olun.

Asenkronize işlevler konusunda çok heyecanlıyım. 2014 ve bu canlıların web'de dolaştığını görmek harika. Hay aksi!