ฟังก์ชันไม่พร้อมกัน: ทำตามสัญญาง่าย

ฟังก์ชันอะซิงโครนัสช่วยให้คุณเขียนโค้ดตามสัญญาได้เสมือนกับว่าเป็นแบบซิงโครนัส

ระบบจะเปิดใช้ฟังก์ชัน Async โดยค่าเริ่มต้นใน Chrome, Edge, Firefox และ Safari และ ก็เป็นเรื่องที่มหัศจรรย์มากทีเดียว ที่ให้คุณเขียนโค้ดตามคำสัญญา หากเป็นแบบซิงโครนัสแต่ไม่บล็อกเทรดหลัก องค์ประกอบเหล่านี้ทำให้ โค้ดแบบอะซิงโครนัส "ฉลาด" น้อยลง และอ่านง่ายขึ้น

ฟังก์ชันอะซิงโครนัสมีการทำงานดังนี้

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

หากใช้คีย์เวิร์ด async ก่อนคําจํากัดความของฟังก์ชัน คุณจะใช้ await ภายในฟังก์ชัน เมื่อคุณawaitให้คำมั่นสัญญา ฟังก์ชันจะหยุดชั่วคราว ด้วยวิธีที่ไม่บล็อกจนกว่าสัญญาจะตกลง หากคำมั่นสัญญาจะทำให้ เพื่อให้คุณได้รับมูลค่าคืน หากคำสัญญาปฏิเสธ ระบบจะส่งค่าที่ถูกปฏิเสธ

การสนับสนุนเบราว์เซอร์

การรองรับเบราว์เซอร์

  • Chrome: 55.
  • ขอบ: 15
  • Firefox: 52
  • Safari: 10.1

แหล่งที่มา

ตัวอย่าง: การบันทึกการดึงข้อมูล

สมมติว่าคุณต้องการดึงข้อมูล URL และบันทึกคำตอบเป็นข้อความ ลักษณะของป้ายเป็นแบบนี้ โดยใช้คำสัญญา:

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

และนี่คือสิ่งเดียวกันนี้เมื่อใช้ฟังก์ชันอะซิงโครนัส:

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

จำนวนบรรทัดเท่ากัน แต่ Callback ทั้งหมดจะหายไป เข้าใจแล้ว อ่านง่ายขึ้น โดยเฉพาะสำหรับผู้ที่ไม่ค่อยคุ้นเคยกับสัญญา

ค่าการแสดงผลแบบไม่พร้อมกัน

ฟังก์ชันอะซิงโครนัสจะให้ผลลัพธ์ทุกครั้ง ไม่ว่าคุณจะใช้ await หรือไม่ก็ตาม นั่น จะแปลงผลลัพธ์โดยที่ฟังก์ชันอะซิงโครนัสส่งคืน หรือปฏิเสธ อะไรก็ตามที่ฟังก์ชันอะซิงโครนัสอัปโหลด ดังนั้นด้วย:

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

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

...การเรียก hello() จะแสดงคำสัญญาว่าจะตอบสนองด้วย "world"

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

...การเรียก foo() จะแสดงการสัญญาว่าปฏิเสธ Error('bar')

ตัวอย่าง: การสตรีมคำตอบ

ประโยชน์ของฟังก์ชันแบบไม่พร้อมกันจะเพิ่มขึ้นเมื่อมีตัวอย่างที่ซับซ้อนมากขึ้น พูดว่าอยากได้ เพื่อสตรีมคำตอบขณะตัดกลุ่มบางส่วนออก และแสดงผลขนาดสุดท้าย

ต่อไปนี้คือสิ่งที่สัญญาไว้

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);
    });
  });
}

ดูสิ Jake "ผู้นำทางแห่งคำสัญญา" ที่เก็บ ดูว่าฉันโทรมาอย่างไร processResult() ภายในตัวเองเพื่อตั้งค่าการวนซ้ำแบบไม่พร้อมกันใช่ไหม งานเขียนที่สร้าง ฉันรู้สึกฉลาดมากเลย แต่ชอบที่ "ฉลาด" ส่วนใหญ่ คุณจะต้องจับตาดูโค้ดนั้น เพื่อค้นหาสิ่งที่กำลังทำอยู่ เช่น รูปภาพดวงตาวิเศษจาก ยุค 90

โปรดลองอีกครั้งด้วยฟังก์ชันอะซิงโครนัส

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;
}

"ฉลาด" ทุกคน หายไปแล้ว การวนซ้ำแบบไม่พร้อมกันที่ทำให้ฉันรู้สึกมั่นใจมากคือ แทนที่ด้วยรายการที่เชื่อถือ น่าเบื่อ และลูป ยิ่งไปกว่านั้น ในอนาคต คุณจะได้รับ ตัวทำซ้ำแบบไม่พร้อมกัน ซึ่งจะ แทนที่ลูป while ด้วยวงแหวนสำหรับเชื่อมต่อ ซึ่งทำให้ดูเรียบร้อยยิ่งขึ้น

ไวยากรณ์ฟังก์ชันแบบไม่พร้อมกันอื่นๆ

เราแสดง async function() {} ให้คุณดูแล้ว แต่คีย์เวิร์ด async สามารถเป็น ที่ใช้กับไวยากรณ์ของฟังก์ชันอื่น

ฟังก์ชันลูกศร

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

เมธอดของออบเจ็กต์

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

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

วิธีการสำหรับชั้นเรียน

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();

ระวังด้วยนะ หลีกเลี่ยงการเรียงลำดับมากเกินไป

แม้ว่าคุณจะกำลังเขียนโค้ดที่ดูพร้อมกัน แต่โปรดไม่พลาด ในการทำสิ่งต่างๆ ไปพร้อมๆ กัน

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

ข้อมูลข้างต้นใช้เวลา 1, 000 มิลลิวินาทีจึงจะเสร็จสมบูรณ์ ในขณะที่

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!';
}

วิธีการข้างต้นใช้เวลา 500 มิลลิวินาทีในการดำเนินการ เนื่องจากการรอทั้งสองแบบเกิดขึ้นพร้อมกัน มาดูตัวอย่างที่ใช้ได้จริงกัน

ตัวอย่าง: การแสดงเอาต์พุตตามลำดับ

สมมติว่าคุณต้องการดึงข้อมูลชุด URL และบันทึกไว้โดยเร็วที่สุดใน ลำดับที่ถูกต้อง

หายใจลึกๆ - ตัวอย่างเส้นทางมีดังนี้

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());
}

ถูกต้อง ฉันใช้ reduce เพื่อผูกการเรียงลำดับคำสัญญา ฉัน คือ อัจฉริยะ แต่วิธีนี้เป็นการเขียนโค้ดที่ฉลาดมากสักหน่อย คุณจะมีประสิทธิภาพมากกว่าหากไม่มี

อย่างไรก็ตาม เมื่อแปลงฟังก์ชันข้างต้นเป็นฟังก์ชันอะซิงโครนัส คุณอาจอยากใช้งาน ตามลำดับมากเกินไป

ไม่แนะนำ - เรียงตามลำดับมากเกินไป
async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
ดูเรียบร้อยกว่ามาก แต่การดึงข้อมูลครั้งที่ 2 ยังไม่เริ่มจนกว่าการดึงข้อมูลครั้งแรกจะเสร็จ ได้รับการอ่านทั้งหมด และอื่นๆ ซึ่งช้ากว่ามากจากตัวอย่างที่สัญญาไว้ว่า จะทำการดึงข้อมูลพร้อมกัน โชคดีที่มีตรงกลางที่เจ๋งๆ
แนะนำ - ดูดีและเป็นคู่ขนาน
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);
  }
}
ในตัวอย่างนี้ URL จะถูกดึงและอ่านพร้อมกัน แต่ URL จะ "อัจฉริยะ" ระบบจะแทนที่บิต reduce ด้วยบิตมาตรฐาน น่าเบื่อ และอ่านได้ง่ายสำหรับวนซ้ำ

วิธีแก้ปัญหาการสนับสนุนเบราว์เซอร์: โปรแกรมสร้าง

หากคุณกำหนดเป้าหมายเป็นเบราว์เซอร์ที่รองรับโปรแกรมสร้าง (ซึ่งรวมถึง เวอร์ชันล่าสุดของเบราว์เซอร์หลักๆ ทั้งหมด ) คุณสามารถจัดเรียงฟังก์ชันอะซิงโครนัส Polyfill ได้

Babel จะดำเนินการดังกล่าวให้คุณ นี่คือตัวอย่างจากการตอบกลับ Babel

ผมขอแนะนำวิธีการเปลี่ยนผ่าน เพราะคุณจะปิดได้เมื่อ เบราว์เซอร์เป้าหมายสนับสนุนฟังก์ชันอะซิงโครนัส แต่ถ้าคุณไม่ต้องการใช้ Transpiler คุณสามารถดำเนินการ โพลีฟิลล์ของ Babel และใช้งานด้วยตัวคุณเอง แทนที่จะเป็น:

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

...คุณจะต้องใส่ polyfill และเขียนว่า

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

โปรดทราบว่าคุณต้องส่งโปรแกรมสร้าง (function*) ไปยัง createAsyncFunction และใช้ yield แทน await นอกจากนี้ ยังทำงานเหมือนเดิม

วิธีแก้ปัญหาเบื้องต้น: เครื่องมือสร้างใหม่

และหากคุณกำหนดเป้าหมายไปที่เบราว์เซอร์รุ่นเก่า Babel ก็สามารถแปลงเครื่องมือสร้าง คุณสามารถใช้ฟังก์ชันอะซิงโครนัสได้จนถึง IE8 หากต้องการดำเนินการนี้ คุณจำเป็นต้อง ค่าที่กำหนดล่วงหน้า es2017 ของ Babel และค่าที่กำหนดล่วงหน้า es2015

เอาต์พุตค่อนข้างไม่ดี ดังนั้นโปรดระวัง และการขยายโค้ด

ไม่ซิงค์ทุกเรื่องเลย!

เมื่อฟังก์ชันอะซิงโครนัสเข้ามาอยู่ในทุกเบราว์เซอร์แล้ว ให้ใช้ฟังก์ชันดังกล่าวในทุก มีฟังก์ชันที่ได้ผลแน่นอน โค้ดเหล่านี้ไม่เพียงแต่ทำให้โค้ดของคุณเป็นระเบียบขึ้น ฟังก์ชันนั้นจะทำให้ได้รับคำสัญญาเสมอ

ฉันตื่นเต้นมากกับฟังก์ชัน แบบไม่พร้อมกันกลับมา 2014 และ เป็นเรื่องดีที่ได้เห็นพวกเขาไปปรากฏตัวจริงบนเบราว์เซอร์ ไชโย!