Bagaimana janji bekerja secara internal javascript?

JavaScript adalah bahasa sinkron pada intinya, artinya hanya satu baris kode yang berjalan setiap saat. Itu berarti waktu yang dihabiskan untuk mengeksekusi beberapa bagian kode menunda eksekusi kode lainnya. Ini termasuk melukis sesuatu di layar, menangani masukan pengguna, atau mengerjakan matematika

Saat membangun aplikasi frontend, kita sering harus berurusan dengan operasi asinkron, seperti input pengguna atau permintaan HTTP. Di browser mereka dapat berjalan di utas terpisah, jadi bagaimana kita menangani asinkronisitas ini dalam bahasa sinkron seperti JavaScript?

Memahami bagian JavaScript ini sangat penting untuk membangun aplikasi dunia nyata. Dan itulah yang akan kita lihat di artikel ini

Kita akan belajar tentang

  1. Panggilan balik
    Bagaimana callback dapat digunakan untuk menangani kode asinkron, dan bagaimana callback mengarah ke callback hell
  2. Janji
    Cara menggunakan janji untuk membuat panggilan balik dapat dikelola. Kami juga akan menulis penerapan API janji dasar kami sendiri untuk memahami cara kerjanya
  3. Asinkron/menunggu
    Pelajari cara menggunakan await/async API yang baru untuk membuat janji lebih mudah digunakan

📚

Sinkronisitas mengurangi beban kognitif saat menulis kode JavaScript karena Anda tidak perlu khawatir dengan banyak utas yang mengakses dan memperbarui data yang sama. Namun, terkadang perlu keluar dari utas tunggal untuk operasi komputasi yang mahal. Untuk memahami cara mengeksekusi kode Anda sendiri secara asinkron, pelajari tentang Web Worker di browser dan Worker Threads di Node.js. js

Fungsi panggilan balik

Tidak praktis jika membuat permintaan HTTP memblokir kode sampai kami mendapat tanggapan. Bahkan jika itu kembali dalam 100 milidetik, itu masih lama dalam waktu komputer. Untungnya, JavaScript memungkinkan kami menyediakan fungsi panggilan balik ke API asinkron

📌

Callback adalah fungsi yang diteruskan sebagai argumen ke fungsi lain untuk dipanggil oleh fungsi tersebut di lain waktu. Seringkali, untuk menunjukkan bahwa beberapa tindakan telah selesai

Bahkan, Anda mungkin menggunakan panggilan balik tanpa memikirkannya

Misalnya, kejadian pengguna bersifat asinkron, dan Anda mendaftarkan fungsi callback yang menangani kejadian tersebut, seperti ini

const btn = document.querySelector("button");

btn.addEventListener("click", () => {
  // Called when the user clicks the button
});
Mendaftarkan event handler

Kode contoh mendaftarkan panggilan balik untuk dieksekusi ketika pengguna mengklik tombol. Sementara itu, program terus menjalankan kode lain dan tidak menunggu input pengguna

📚

Perlu diketahui bahwa kode event handler mungkin tidak segera dijalankan setelah pengguna mengklik tombol. Pelajari tentang loop peristiwa untuk memahami alasannya

Masalah dengan callback

Panggilan balik adalah cara paling dasar untuk menangani kode asinkron. Mereka sebenarnya bisa membuat Anda cukup jauh, tetapi pada titik tertentu, mereka akan mulai merasa membatasi

  • Callback membuat tipuan aliran kode karena hasil fungsi tidak dikembalikan tetapi diteruskan ke callback. Ini membuat kode lebih sulit untuk diikuti
  • Menangani error dalam callback lebih merupakan masalah konvensi daripada API, yang menciptakan inkonsistensi dan beban kognitif
  • Panggilan balik bersarang dapat mengarah ke 'neraka panggilan balik'. Saat itulah panggilan balik bersarang di dalam panggilan balik lain, sedalam beberapa level, seringkali membuat kode sulit dibaca dan dipahami

Pertimbangkan contoh kode JavaScript asinkron ini menggunakan callback

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
Hanya sedalam 3 level, tetapi sarang sudah menjadi berlebihan

Kodenya tidak terlalu rumit, tapi sudah tidak enak dibaca, karena bersarang. Sekarang pertimbangkan bahwa dalam aplikasi dunia nyata kita perlu sedikit berurusan dengan kode asinkron, dan Anda akan menyadari mengapa segera terasa seperti berada di neraka

😅

Bentuk piramida yang dibentuk oleh callback bersarang ini disebut 'piramida malapetaka', karena lekukannya yang terlihat seperti piramida menyamping

Janji

Janji adalah objek JavaScript yang mewakili nilai yang akan tersedia setelah operasi asinkron selesai. Janji dapat dikembalikan secara sinkron seperti nilai biasa, tetapi nilai dapat diberikan di lain waktu

Itu juga menyediakan API untuk mengakses nilai "yang dijanjikan" dengan mengikat callback untuk status janji yang berbeda

const promise = new Promise((resolve, reject) => {
  // This function runs an asyncronous operation that will call:
  // resolve(value) - if it succeeded, with the resolved value;
  // reject(reason) - if it failed, with the error set as value;
});
Sintaks konstruktor untuk membuat janji JavaScript

Janji menyatakan

Bergantung pada hasil operasi asinkron, promise bisa berada dalam tiga status

  • tertunda. atur awalnya, saat operasi asinkron sedang berlangsung;
  • terpenuhi. operasi asinkron telah selesai dengan sukses;
  • ditolak. operasi telah gagal
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
Janji tertunda selama 1 detik, kemudian dipenuhi dengan nilai string "selesai. "

Ketika sebuah janji memasuki keadaan dipenuhi atau ditolak, itu dikatakan diselesaikan. Janji secara formal dikatakan diselesaikan jika diselesaikan atau diselesaikan dengan janji sehingga penyelesaian atau penolakan lebih lanjut tidak ada pengaruhnya. Namun, bahasa sehari-hari sering berarti bahwa janji itu dipenuhi

📚

Jika Anda tertarik dengan definisi formal, lihat spesifikasi Promise/A+

Bekerja dengan janji

Janji bertindak sebagai nilai sinkron - dapat diteruskan sebagai argumen atau dikembalikan oleh fungsi seperti biasa. Namun, untuk membaca nilai akhirnya, Anda perlu "berlangganan" padanya. Untuk memungkinkan itu, janji memiliki metode publik

  • new Promise((resolve, reject) => {
      setTimeout(() => resolve("done!"), 1000);
    });
    1 - adalah metode utama yang menerima panggilan balik untuk dijalankan saat berhasil dan gagal;
  • new Promise((resolve, reject) => {
      setTimeout(() => resolve("done!"), 1000);
    });
    2 - adalah metode untuk mendaftarkan callback ketika janji ditolak;
  • new Promise((resolve, reject) => {
      setTimeout(() => resolve("done!"), 1000);
    });
    3 - adalah metode untuk mendaftarkan panggilan balik untuk dieksekusi setelah janji dipenuhi atau ditolak

Beginilah tampilannya dalam praktik

const promise = new Promise((resolve, reject) => {
  // run async code
})

promise.then(
  (value) => { /* handle a fulfilled promise result */ },
  (reason) => { /* handle a rejected promise result */ }
)

promise.catch(
  (reason) => { /* handle a rejected promise result */ },
)

promise.finally(
  () => { /* handle a any result */ }
)
Metode utama janji JavaScript

Selama janji tertunda, callback tidak akan dieksekusi. Setelah janji diselesaikan, itu akan selalu memegang nilai hasil. Jika handler dilampirkan ke handler yang sudah diselesaikan, handler langsung lari dan menerima nilai promise

Rantai janji

Sebuah fitur yang sangat berguna dari promise adalah

new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
_4,
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
5 dan
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
6 memanggil return promise, yang berarti bahwa kita dapat melampirkan banyak penangan hanya dengan merangkai panggilan bersama-sama

const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
Menggabungkan penangan janji bersama-sama

Setiap

new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
_7 penangan akan menerima hasil yang dikembalikan dari pemanggilan penangan sebelumnya. Jika terjadi kesalahan di salah satu hander, janji akan memasuki status ditolak dan penangan selanjutnya yang dipanggil adalah
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
8 dan
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
5

Catatan. kesalahan pemula yang umum adalah melampirkan banyak penangan pada janji asli saat mencoba membuat rantai, itu tidak akan berhasil, tetapi ini mungkin fitur yang berguna dalam beberapa kasus

const promise = new Promise((resolve, reject) => {
  resolve(1);
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});
Setiap panggilan
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
4 mengembalikan janji baru dengan nilai yang bertambah, tetapi penangan melekat pada janji asli yang diselesaikan dengan nilai 1

Ingat fungsi kita yang membuat teh?

function makeTea() {
  const teapot = getTeapot();
  const kettle = getKettle();

  kettle.fill();

  const teaPromise = kettle
    .boil() // Assuming boil function returns a promise
    .catch((error) => {
      // Only handles kettle issues
      // Rethrows the error keep the rejected the promise state
      throw new Error("Kettle broke 😭");
    })
    .then(water => {
      kettle.pour(water, teapot);
      return waitMinutes(5); // Assuming waitMinutes returns a promise
    }).then(() => {
      const cup = getCup();
      const tea = teapot.pourInto(cup);
      return waitMinutes(5)
        .then(() => tea); // Our promise chain must return the cup of tea
    });

  addTeaLeaves(teapot);
  
  return teaPromise;
}
Fungsi makeTea ditulis ulang untuk menggunakan promise

Terus terang, itu masih tidak cantik, tetapi ada sedikit sarang dan tidak terus masuk lebih dalam, karena sebagian besar janji dirantai ke janji asli

Metode statis

Janji memiliki beberapa metode statis untuk membantu mempermudah pengerjaannya. Yang paling penting adalah

  • const promise = new Promise((resolve, reject) => {
      // run async code
    })
    
    promise.then(
      (value) => { /* handle a fulfilled promise result */ },
      (reason) => { /* handle a rejected promise result */ }
    )
    
    promise.catch(
      (reason) => { /* handle a rejected promise result */ },
    )
    
    promise.finally(
      () => { /* handle a any result */ }
    )
    1 - membuat janji yang diselesaikan dengan nilai yang diberikan;
  • const promise = new Promise((resolve, reject) => {
      // run async code
    })
    
    promise.then(
      (value) => { /* handle a fulfilled promise result */ },
      (reason) => { /* handle a rejected promise result */ }
    )
    
    promise.catch(
      (reason) => { /* handle a rejected promise result */ },
    )
    
    promise.finally(
      () => { /* handle a any result */ }
    )
    2 - membuat janji yang ditolak dengan nilai yang diberikan;
  • const promise = new Promise((resolve, reject) => {
      // run async code
    })
    
    promise.then(
      (value) => { /* handle a fulfilled promise result */ },
      (reason) => { /* handle a rejected promise result */ }
    )
    
    promise.catch(
      (reason) => { /* handle a rejected promise result */ },
    )
    
    promise.finally(
      () => { /* handle a any result */ }
    )
    3 - membuat janji dengan nilai janji pertama yang dipenuhi atau ditolak dalam larik yang disediakan;
  • const promise = new Promise((resolve, reject) => {
      // run async code
    })
    
    promise.then(
      (value) => { /* handle a fulfilled promise result */ },
      (reason) => { /* handle a rejected promise result */ }
    )
    
    promise.catch(
      (reason) => { /* handle a rejected promise result */ },
    )
    
    promise.finally(
      () => { /* handle a any result */ }
    )
    4 - membuat janji dengan nilai janji pertama yang dipenuhi dalam array, jika tidak ada, maka janji itu ditolak
  • const promise = new Promise((resolve, reject) => {
      // run async code
    })
    
    promise.then(
      (value) => { /* handle a fulfilled promise result */ },
      (reason) => { /* handle a rejected promise result */ }
    )
    
    promise.catch(
      (reason) => { /* handle a rejected promise result */ },
    )
    
    promise.finally(
      () => { /* handle a any result */ }
    )
    5 - membuat janji yang menyelesaikan ke array dari nilai janji yang dipenuhi. Jika ada janji dalam array yang ditolak, janji yang dikembalikan akan ditolak
  • const promise = new Promise((resolve, reject) => {
      // run async code
    })
    
    promise.then(
      (value) => { /* handle a fulfilled promise result */ },
      (reason) => { /* handle a rejected promise result */ }
    )
    
    promise.catch(
      (reason) => { /* handle a rejected promise result */ },
    )
    
    promise.finally(
      () => { /* handle a any result */ }
    )
    6 - membuat janji yang diselesaikan ketika semua janji dalam array telah diselesaikan (terpenuhi atau ditolak). Sebagai nilai, ia menerima larik objek, yang berisi properti
    const promise = new Promise((resolve, reject) => {
      // run async code
    })
    
    promise.then(
      (value) => { /* handle a fulfilled promise result */ },
      (reason) => { /* handle a rejected promise result */ }
    )
    
    promise.catch(
      (reason) => { /* handle a rejected promise result */ },
    )
    
    promise.finally(
      () => { /* handle a any result */ }
    )
    7 dan
    const promise = new Promise((resolve, reject) => {
      // run async code
    })
    
    promise.then(
      (value) => { /* handle a fulfilled promise result */ },
      (reason) => { /* handle a rejected promise result */ }
    )
    
    promise.catch(
      (reason) => { /* handle a rejected promise result */ },
    )
    
    promise.finally(
      () => { /* handle a any result */ }
    )
    8 atau
    const promise = new Promise((resolve, reject) => {
      // run async code
    })
    
    promise.then(
      (value) => { /* handle a fulfilled promise result */ },
      (reason) => { /* handle a rejected promise result */ }
    )
    
    promise.catch(
      (reason) => { /* handle a rejected promise result */ },
    )
    
    promise.finally(
      () => { /* handle a any result */ }
    )
    9, yang mewakili hasil dari setiap janji

Untuk benar-benar memahami cara kerja janji, saya menyarankan Anda untuk memahami cara penerapannya. Saya jamin sebagian besar pengembang frontend tidak akan dapat mengimplementasikan janji, hanya karena mereka tidak memahaminya

Nah, sekarang Anda akan melakukannya

Menerapkan Basic Promise API

Janji adalah dasar untuk melakukan pekerjaan asinkron dalam JavaScript, jadi sangat penting untuk memahaminya sepenuhnya. Saat saya baru memulai dengan JavaScript, beberapa aspek dari promise tampak ajaib. Namun, itu hanya menunjukkan pemahaman saya yang tidak lengkap

Cara terbaik untuk menghilangkan sihir apa pun adalah membangun sesuatu dari awal. Dengan begitu, kita dapat memahami cara kerja berbagai hal di bawah tenda. Percayalah pada saya ketika saya mengatakan ini - jika Anda mempelajari cara menerapkan API janji dari awal, Anda akan memahaminya lebih baik daripada kebanyakan pengembang perangkat lunak

Untuk tujuan kami, saya akan menyebut janji kami

const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
0, dengan API yang sama

1) Tes

Mari kita mulai dengan menulis beberapa pengujian, sehingga kami yakin bahwa kami mencakup API lengkap. Kami tidak akan menghabiskan waktu di bagian ini, jadi jika Anda mengikuti, cukup salin-tempel kodenya. Kami ingin menguji setidaknya kasus-kasus ini

  1. Janji bisa diselesaikan dengan nilai;
  2. Janji dapat ditolak dengan memanggil
    const promise = new Promise((resolve, reject) => {
      // handle async code
    });
    
    promise
      .then(handleFulfilledA, handleRejectedA)
      .then(handleFulfilledB)
      .then(handleFulfilledC, handleRejectedC)
      .catch(handleRejectedAny)
      .finally(handleResolved)
    1 atau membuat kesalahan;
  3. Janji itu bisa ditolak atau diselesaikan dengan janji lain;
  4. Janji yang diselesaikan dan ditolak tidak dapat ditolak atau diselesaikan lebih lanjut;
  5. Beberapa penangan
    const promise = new Promise((resolve, reject) => {
      // handle async code
    });
    
    promise
      .then(handleFulfilledA, handleRejectedA)
      .then(handleFulfilledB)
      .then(handleFulfilledC, handleRejectedC)
      .catch(handleRejectedAny)
      .finally(handleResolved)
    2,
    const promise = new Promise((resolve, reject) => {
      // handle async code
    });
    
    promise
      .then(handleFulfilledA, handleRejectedA)
      .then(handleFulfilledB)
      .then(handleFulfilledC, handleRejectedC)
      .catch(handleRejectedAny)
      .finally(handleResolved)
    3,
    const promise = new Promise((resolve, reject) => {
      // handle async code
    });
    
    promise
      .then(handleFulfilledA, handleRejectedA)
      .then(handleFulfilledB)
      .then(handleFulfilledC, handleRejectedC)
      .catch(handleRejectedAny)
      .finally(handleResolved)
    4 dapat dirantai;
  6. const promise = new Promise((resolve, reject) => {
      // handle async code
    });
    
    promise
      .then(handleFulfilledA, handleRejectedA)
      .then(handleFulfilledB)
      .then(handleFulfilledC, handleRejectedC)
      .catch(handleRejectedAny)
      .finally(handleResolved)
    _4 harus dipanggil untuk penolakan atau pemenuhan
  7. Operasi janji harus selalu asinkron;

Inilah test suite yang akan saya gunakan

import Futurable from "./Futurable";

describe("Futurable <constructor>", () => {
  it(`returns a promise-like object,
      that resolves it's chain after invoking <resolve>`, (done) => {
    new Futurable<string>((resolve) => {
      setTimeout(() => {
        resolve("testing");
      }, 20);
    }).then((val) => {
      expect(val).toBe("testing");
      done();
    });
  });

  it("is always asynchronous", () => {
    let value = "no";
    new Futurable<string>((resolve) => {
      value = "yes;";
      resolve(value);
    });
    expect(value).toBe("no");
  });

  it("resolves with the returned value", (done) => {
    new Futurable<string>((resolve) => resolve("testing")).then((val) => {
      expect(val).toBe("testing");
      done();
    });
  });

  it("resolves a Futurable before calling <then>", (done) => {
    new Futurable<string>((resolve) =>
      resolve(new Futurable((resolve) => resolve("testing")))
    ).then((val) => {
      expect(val).toBe("testing");
      done();
    });
  });

  it("resolves a Futurable before calling <catch>", (done) => {
    new Futurable<string>((resolve) =>
      resolve(new Futurable((_, reject) => reject("fail")))
    ).catch((reason) => {
      expect(reason).toBe("fail");
      done();
    });
  });

  it("catches errors from <reject>", (done) => {
    const error = new Error("Why u fail?");

    new Futurable((_, reject) => {
      return reject(error);
    }).catch((err: Error) => {
      expect(err).toBe(error);
      done();
    });
  });

  it("catches errors from <throw>", (done) => {
    const error = new Error("Why u fail?");

    new Futurable(() => {
      throw error;
    }).catch((err) => {
      expect(err).toBe(error);
      done();
    });
  });

  it("does not change state anymore after promise is fulfilled", (done) => {
    new Futurable((resolve, reject) => {
      resolve("success");
      reject("fail");
    })
      .catch(() => {
        done.fail(new Error("Should not be called"));
      })
      .then((value) => {
        expect(value).toBe("success");
        done();
      });
  });

  it("does not change state anymore after promise is rejected", (done) => {
    new Futurable((resolve, reject) => {
      reject("fail");
      resolve("success");
    })
      .then(() => {
        done.fail(new Error("Should not be called"));
      })
      .catch((err) => {
        expect(err).toBe("fail");
        done();
      });
  });
});

describe("Futurable chaining", () => {
  it("resolves chained <then>", (done) => {
    new Futurable<number>((resolve) => {
      resolve(0);
    })
      .then((value) => value + 1)
      .then((value) => value + 1)
      .then((value) => value + 1)
      .then((value) => {
        expect(value).toBe(3);
        done();
      });
  });

  it("resolves <then> chain after <catch>", (done) => {
    new Futurable<number>(() => {
      throw new Error("Why u fail?");
    })
      .catch(() => {
        return "testing";
      })
      .then((value) => {
        expect(value).toBe("testing");
        done();
      });
  });

  it("catches errors thrown in <then>", (done) => {
    const error = new Error("Why u fail?");

    new Futurable((resolve) => {
      resolve();
    })
      .then(() => {
        throw error;
      })
      .catch((err) => {
        expect(err).toBe(error);
        done();
      });
  });

  it("catches errors thrown in <catch>", (done) => {
    const error = new Error("Final error");

    new Futurable((_, reject) => {
      reject(new Error("Initial error"));
    })
      .catch(() => {
        throw error;
      })
      .catch((err) => {
        expect(err).toBe(error);
        done();
      });
  });

  it("short-circuits <then> chain on error", (done) => {
    const error = new Error("Why u fail?");

    new Futurable(() => {
      throw error;
    })
      .then(() => {
        done.fail(new Error("Should not be called"));
      })
      .catch((err) => {
        expect(err).toBe(error);
        done();
      });
  });

  it("passes value through undefined <then>", (done) => {
    new Futurable((resolve) => {
      resolve("testing");
    })
      .then()
      .then((value) => {
        expect(value).toBe("testing");
        done();
      });
  });

  it("passes value through undefined <catch>", (done) => {
    const error = new Error("Why u fail?");

    new Futurable((_, reject) => {
      reject(error);
    })
      .catch()
      .catch((err) => {
        expect(err).toBe(error);
        done();
      });
  });
});

describe("Futurable <finally>", () => {
  it("it is called when Futurable is resolved", (done) => {
    new Futurable((resolve) => resolve("success")).finally(() => {
      done();
    });
  });

  it("it is called when Futurable is rejected", (done) => {
    new Futurable((_, reject) => reject("fail")).finally(() => {
      done();
    });
  });

  it("it preserves a resolved promise state", (done) => {
    let finallyCalledTimes = 0;

    new Futurable((resolve) => resolve("success"))
      .finally(() => {
        finallyCalledTimes += 1;
      })
      .then((value) => {
        expect(value).toBe("success");
        expect(finallyCalledTimes).toBe(1);
        done();
      });
  });

  it("it preserves a rejected promise state", (done) => {
    let finallyCalledTimes = 0;

    new Futurable((_, reject) => reject("fail"))
      .finally(() => {
        finallyCalledTimes += 1;
      })
      .catch((reason) => {
        expect(reason).toBe("fail");
        expect(finallyCalledTimes).toBe(1);
        done();
      });
  });
});
Rangkaian pengujian untuk implementasi janji kustom

👉

Jika Anda ingin mengikuti, jangan ragu untuk membuat kode naskah dan kotak dan rekatkan kode ini. Atau garpu tambang dan hapus file

const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
6 jika Anda ingin memulai dari awal. Beralih ke tab "Tes" untuk melihat hasilnya

2) Antarmuka

Sekarang kita beralih ke implementasi, dan hal pertama yang perlu kita lakukan adalah mendefinisikan API publik

class Futurable<T> {
  constructor(
    executor: (
      resolve: (value: T | Futurable<T>) => void,
      reject: (reason?: any) => void
    ) => void
  ) {
    // TODO: implement
  }

  then = <R1 = T, R2 = never>(
    onFulfilled?: (value: T) => R1 | Futurable<R1>,
    onRejected?: (reason: any) => R2 | Futurable<R2>
  ) => {
    // TODO: implement
  };

  catch = <R = never>(onRejected?: (reason: any) => R | Futurable<R>) => {
    // TODO: implement
  };

  finally = (onFinally: () => void): Futurable<T> => {
    // TODO: implement
  };
}

export default Futurable<T>;
Metode publik dari implementasi janji kustom kami

Saya mengambil sebagian besar tipe dari definisi tipe janji ES6 sehingga implementasi kami sedekat mungkin dengan standar

Konstruktor kami akan mengambil callback sebagai parameter dan menyebutnya lewat fungsi

const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
7 dan
const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
8

Ingat bagaimana saya menyebutkan bahwa

const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
_2 menerima panggilan balik untuk pemenuhan dan penolakan? . Ini akan menjadi dasar untuk mendaftarkan penangan kami - tangkap dan akhirnya hanya 'gula', seperti yang akan Anda lihat nanti

3) Mesin negara

Saya telah menyebutkan bagaimana sebuah janji memiliki 3 status. terpenuhi, ditolak, dan tertunda. Ini dapat dinyatakan sebagai mesin negara

Bagaimana janji bekerja secara internal javascript?
Diagram untuk transisi status janji

Janji dimulai sebagai tertunda dan bergerak ke keadaan terselesaikan atau keadaan ditolak. Setelah melakukan transisi, itu tidak dapat lagi diselesaikan, ditolak, atau memasuki status tertunda

Untuk mengimplementasikannya, pertama, kita perlu mendefinisikan keadaan internal dan hasil dari promise, mari kita lakukan

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
_0

Sekarang kita bisa mengimplementasikan fungsi yang menangani transisi ini

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
_1

Metode

const promise = new Promise((resolve, reject) => {
  resolve(1);
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});
_0 sekarang bertanggung jawab untuk membuat transisi dan mengatur hasil janji, mari kita pahami apa yang dilakukan kode

  1. Kami hanya perlu mengizinkan transisi dari status tertunda, jika tidak, memanggil
    const promise = new Promise((resolve, reject) => {
      resolve(1);
    });
    
    promise.then((result) => {
      console.log(result); // 1
      return result + 1;
    });
    
    promise.then((result) => {
      console.log(result); // 1
      return result + 1;
    });
    
    promise.then((result) => {
      console.log(result); // 1
      return result + 1;
    });
    0 tidak akan melakukan apa-apa, jadi kami keluar dari fungsi dengan mengembalikan
  2. Kami memeriksa apakah nilai yang diberikan itu sendiri merupakan janji dan menyelesaikannya alih-alih mentransisikan status. Kami juga mendefinisikan
    const promise = new Promise((resolve, reject) => {
      // handle async code
    });
    
    promise
      .then(handleFulfilledA, handleRejectedA)
      .then(handleFulfilledB)
      .then(handleFulfilledC, handleRejectedC)
      .catch(handleRejectedAny)
      .finally(handleResolved)
    7 dan
    const promise = new Promise((resolve, reject) => {
      // handle async code
    });
    
    promise
      .then(handleFulfilledA, handleRejectedA)
      .then(handleFulfilledB)
      .then(handleFulfilledC, handleRejectedC)
      .catch(handleRejectedAny)
      .finally(handleResolved)
    8 fungsi internal, yang akan kami implementasikan nanti
  3. Jika nilainya bukan janji, kami mentransisikan status dan nilai

📌

Menurut standar janji, jika suatu objek berisi metode

const promise = new Promise((resolve, reject) => {
  resolve(1);
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});
4, maka itu harus bertindak seperti janji, bahkan jika itu bukan turunan dari kelas
const promise = new Promise((resolve, reject) => {
  resolve(1);
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});
5 - itu disebut kemudian dapat. Nah, itu cukup keren. Artinya, kita dapat menyelesaikan
const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
_0 dengan
const promise = new Promise((resolve, reject) => {
  resolve(1);
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});
7 sebagai hasilnya dan itu akan tetap berfungsi, dan sebaliknya

4) Menyelesaikan / Menolak

Sekarang kita memiliki fungsi yang menangani transisi status janji kita, kita dapat menggunakannya untuk mengimplementasikan konstruktor dan menyelesaikan/menolak fungsi. Sesederhana memanggil fungsi

const promise = new Promise((resolve, reject) => {
  resolve(1);
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});
0 kami di
const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
7 dan
const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
8 fungsi dan kemudian menggunakannya di konstruktor kami

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
2Implementasi konstruktor dan menyelesaikan / menolak fungsi janji kustom

Anda mungkin memperhatikan blok try-catch dan bertanya-tanya mengapa kami membutuhkannya di konstruktor. Itu karena memanggil

const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
_8 bukan satu-satunya cara untuk menolak janji, kita juga bisa
function makeTea() {
  const teapot = getTeapot();
  const kettle = getKettle();

  kettle.fill();

  const teaPromise = kettle
    .boil() // Assuming boil function returns a promise
    .catch((error) => {
      // Only handles kettle issues
      // Rethrows the error keep the rejected the promise state
      throw new Error("Kettle broke 😭");
    })
    .then(water => {
      kettle.pour(water, teapot);
      return waitMinutes(5); // Assuming waitMinutes returns a promise
    }).then(() => {
      const cup = getCup();
      const tea = teapot.pourInto(cup);
      return waitMinutes(5)
        .then(() => tea); // Our promise chain must return the cup of tea
    });

  addTeaLeaves(teapot);
  
  return teaPromise;
}
2 kesalahan. Melakukan itu akan memicu
function makeTea() {
  const teapot = getTeapot();
  const kettle = getKettle();

  kettle.fill();

  const teaPromise = kettle
    .boil() // Assuming boil function returns a promise
    .catch((error) => {
      // Only handles kettle issues
      // Rethrows the error keep the rejected the promise state
      throw new Error("Kettle broke 😭");
    })
    .then(water => {
      kettle.pour(water, teapot);
      return waitMinutes(5); // Assuming waitMinutes returns a promise
    }).then(() => {
      const cup = getCup();
      const tea = teapot.pourInto(cup);
      return waitMinutes(5)
        .then(() => tea); // Our promise chain must return the cup of tea
    });

  addTeaLeaves(teapot);
  
  return teaPromise;
}
_3 blok kami, dan kami menangani kesalahan dengan menolak janji

Sekarang kita memiliki kemampuan untuk membuat janji

const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
0 baru dan menjalankan callback. Menyelesaikan atau menolaknya dalam panggilan balik akan mengubah status janji internal dan menetapkan nilainya

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
_3

Namun, ada satu masalah

5) Menjalankan janji secara tidak sinkron

Membuat janji baru akan segera mengeksekusi callback. Ini berarti bahwa Janji kami memblokir eksekusi kode, yang berarti itu sebenarnya bukan operasi asinkron

Jika Anda ingin melihat masalahnya beraksi, jalankan kode ini

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
4Konstruktor dijalankan secara sinkron, kode asinkron tidak akan segera menjalankan callback

Mari kita perbaiki

Tanpa mendalami cara kerja loop peristiwa, setidaknya kita harus tahu bahwa kode kita dijalankan secara sinkron dalam fase ini

  • Melaksanakan tugas - kode Anda;
  • Pelaksana pekerjaan - panggilan balik Janji ES6 asli;
  • Menjalankan pesan - acara, panggilan balik, permintaan ajax, dll
Jika Anda ingin penjelasan menyeluruh tentang cara kerja loop acara, tonton presentasi yang luar biasa ini

Artinya dalam praktiknya adalah bahwa kode Anda dijalankan secara sinkron pada prioritas tertinggi - selama itu berjalan, itu akan memblokir eksekusi kode apa pun yang mengikuti. Saat fase eksekusi kode selesai, itu akan memproses semua pekerjaan dan pesan yang telah diterima sementara itu

😉

Jika Anda ingin melihat apa yang saya maksud dengan memblokir kode, jalankan

function makeTea() {
  const teapot = getTeapot();
  const kettle = getKettle();

  kettle.fill();

  const teaPromise = kettle
    .boil() // Assuming boil function returns a promise
    .catch((error) => {
      // Only handles kettle issues
      // Rethrows the error keep the rejected the promise state
      throw new Error("Kettle broke 😭");
    })
    .then(water => {
      kettle.pour(water, teapot);
      return waitMinutes(5); // Assuming waitMinutes returns a promise
    }).then(() => {
      const cup = getCup();
      const tea = teapot.pourInto(cup);
      return waitMinutes(5)
        .then(() => tea); // Our promise chain must return the cup of tea
    });

  addTeaLeaves(teapot);
  
  return teaPromise;
}
5 di konsol browser Anda misalnya. Anda tidak akan pernah melihat pesan 'tidak memblokir'. Lakukan dengan risiko Anda sendiri, Anda mungkin tidak dapat menutup tab lagi dan mungkin harus mematikan browser

Karena kita sedang melakukan implementasi kustom dari promise, kita berasumsi bahwa tidak ada dukungan asli untuk Promises, tetapi kita masih perlu mengeksekusi callback

const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
0 secara asinkron. Untuk melakukan itu, kita dapat menggunakan peretasan yang terkenal.
function makeTea() {
  const teapot = getTeapot();
  const kettle = getKettle();

  kettle.fill();

  const teaPromise = kettle
    .boil() // Assuming boil function returns a promise
    .catch((error) => {
      // Only handles kettle issues
      // Rethrows the error keep the rejected the promise state
      throw new Error("Kettle broke 😭");
    })
    .then(water => {
      kettle.pour(water, teapot);
      return waitMinutes(5); // Assuming waitMinutes returns a promise
    }).then(() => {
      const cup = getCup();
      const tea = teapot.pourInto(cup);
      return waitMinutes(5)
        .then(() => tea); // Our promise chain must return the cup of tea
    });

  addTeaLeaves(teapot);
  
  return teaPromise;
}
7

Ini akan mendelegasikan eksekusi panggilan balik untuk dijalankan segera setelah kode sinkron apa pun, karenanya nol milidetik. Mari kita menulis fungsi utilitas

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
5Menggunakan batas waktu untuk mengeksekusi callback secara asinkron

Kami sekarang dapat membungkus kode konstruktor dan

function makeTea() {
  const teapot = getTeapot();
  const kettle = getKettle();

  kettle.fill();

  const teaPromise = kettle
    .boil() // Assuming boil function returns a promise
    .catch((error) => {
      // Only handles kettle issues
      // Rethrows the error keep the rejected the promise state
      throw new Error("Kettle broke 😭");
    })
    .then(water => {
      kettle.pour(water, teapot);
      return waitMinutes(5); // Assuming waitMinutes returns a promise
    }).then(() => {
      const cup = getCup();
      const tea = teapot.pourInto(cup);
      return waitMinutes(5)
        .then(() => tea); // Our promise chain must return the cup of tea
    });

  addTeaLeaves(teapot);
  
  return teaPromise;
}
8 kami dengan utilitas ini untuk menjalankannya secara asinkron

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
6Menggunakan utilitas
function makeTea() {
  const teapot = getTeapot();
  const kettle = getKettle();

  kettle.fill();

  const teaPromise = kettle
    .boil() // Assuming boil function returns a promise
    .catch((error) => {
      // Only handles kettle issues
      // Rethrows the error keep the rejected the promise state
      throw new Error("Kettle broke 😭");
    })
    .then(water => {
      kettle.pour(water, teapot);
      return waitMinutes(5); // Assuming waitMinutes returns a promise
    }).then(() => {
      const cup = getCup();
      const tea = teapot.pourInto(cup);
      return waitMinutes(5)
        .then(() => tea); // Our promise chain must return the cup of tea
    });

  addTeaLeaves(teapot);
  
  return teaPromise;
}
9 untuk membuat callback kita berjalan secara asinkron

Jika Anda menguji contoh sebelumnya lagi, Anda akan melihat bahwa ia mencetak

import Futurable from "./Futurable";

describe("Futurable <constructor>", () => {
  it(`returns a promise-like object,
      that resolves it's chain after invoking <resolve>`, (done) => {
    new Futurable<string>((resolve) => {
      setTimeout(() => {
        resolve("testing");
      }, 20);
    }).then((val) => {
      expect(val).toBe("testing");
      done();
    });
  });

  it("is always asynchronous", () => {
    let value = "no";
    new Futurable<string>((resolve) => {
      value = "yes;";
      resolve(value);
    });
    expect(value).toBe("no");
  });

  it("resolves with the returned value", (done) => {
    new Futurable<string>((resolve) => resolve("testing")).then((val) => {
      expect(val).toBe("testing");
      done();
    });
  });

  it("resolves a Futurable before calling <then>", (done) => {
    new Futurable<string>((resolve) =>
      resolve(new Futurable((resolve) => resolve("testing")))
    ).then((val) => {
      expect(val).toBe("testing");
      done();
    });
  });

  it("resolves a Futurable before calling <catch>", (done) => {
    new Futurable<string>((resolve) =>
      resolve(new Futurable((_, reject) => reject("fail")))
    ).catch((reason) => {
      expect(reason).toBe("fail");
      done();
    });
  });

  it("catches errors from <reject>", (done) => {
    const error = new Error("Why u fail?");

    new Futurable((_, reject) => {
      return reject(error);
    }).catch((err: Error) => {
      expect(err).toBe(error);
      done();
    });
  });

  it("catches errors from <throw>", (done) => {
    const error = new Error("Why u fail?");

    new Futurable(() => {
      throw error;
    }).catch((err) => {
      expect(err).toBe(error);
      done();
    });
  });

  it("does not change state anymore after promise is fulfilled", (done) => {
    new Futurable((resolve, reject) => {
      resolve("success");
      reject("fail");
    })
      .catch(() => {
        done.fail(new Error("Should not be called"));
      })
      .then((value) => {
        expect(value).toBe("success");
        done();
      });
  });

  it("does not change state anymore after promise is rejected", (done) => {
    new Futurable((resolve, reject) => {
      reject("fail");
      resolve("success");
    })
      .then(() => {
        done.fail(new Error("Should not be called"));
      })
      .catch((err) => {
        expect(err).toBe("fail");
        done();
      });
  });
});

describe("Futurable chaining", () => {
  it("resolves chained <then>", (done) => {
    new Futurable<number>((resolve) => {
      resolve(0);
    })
      .then((value) => value + 1)
      .then((value) => value + 1)
      .then((value) => value + 1)
      .then((value) => {
        expect(value).toBe(3);
        done();
      });
  });

  it("resolves <then> chain after <catch>", (done) => {
    new Futurable<number>(() => {
      throw new Error("Why u fail?");
    })
      .catch(() => {
        return "testing";
      })
      .then((value) => {
        expect(value).toBe("testing");
        done();
      });
  });

  it("catches errors thrown in <then>", (done) => {
    const error = new Error("Why u fail?");

    new Futurable((resolve) => {
      resolve();
    })
      .then(() => {
        throw error;
      })
      .catch((err) => {
        expect(err).toBe(error);
        done();
      });
  });

  it("catches errors thrown in <catch>", (done) => {
    const error = new Error("Final error");

    new Futurable((_, reject) => {
      reject(new Error("Initial error"));
    })
      .catch(() => {
        throw error;
      })
      .catch((err) => {
        expect(err).toBe(error);
        done();
      });
  });

  it("short-circuits <then> chain on error", (done) => {
    const error = new Error("Why u fail?");

    new Futurable(() => {
      throw error;
    })
      .then(() => {
        done.fail(new Error("Should not be called"));
      })
      .catch((err) => {
        expect(err).toBe(error);
        done();
      });
  });

  it("passes value through undefined <then>", (done) => {
    new Futurable((resolve) => {
      resolve("testing");
    })
      .then()
      .then((value) => {
        expect(value).toBe("testing");
        done();
      });
  });

  it("passes value through undefined <catch>", (done) => {
    const error = new Error("Why u fail?");

    new Futurable((_, reject) => {
      reject(error);
    })
      .catch()
      .catch((err) => {
        expect(err).toBe(error);
        done();
      });
  });
});

describe("Futurable <finally>", () => {
  it("it is called when Futurable is resolved", (done) => {
    new Futurable((resolve) => resolve("success")).finally(() => {
      done();
    });
  });

  it("it is called when Futurable is rejected", (done) => {
    new Futurable((_, reject) => reject("fail")).finally(() => {
      done();
    });
  });

  it("it preserves a resolved promise state", (done) => {
    let finallyCalledTimes = 0;

    new Futurable((resolve) => resolve("success"))
      .finally(() => {
        finallyCalledTimes += 1;
      })
      .then((value) => {
        expect(value).toBe("success");
        expect(finallyCalledTimes).toBe(1);
        done();
      });
  });

  it("it preserves a rejected promise state", (done) => {
    let finallyCalledTimes = 0;

    new Futurable((_, reject) => reject("fail"))
      .finally(() => {
        finallyCalledTimes += 1;
      })
      .catch((reason) => {
        expect(reason).toBe("fail");
        expect(finallyCalledTimes).toBe(1);
        done();
      });
  });
});
0, yang berarti kode panggilan balik janji tidak segera dijalankan dan tidak lagi memblokir eksekusi kode

6) Pelaksana penangan

Janji memungkinkan kita untuk mengakses nilainya dengan melampirkan

const promise = new Promise((resolve, reject) => {
  resolve(1);
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});
4 atau
function makeTea() {
  const teapot = getTeapot();
  const kettle = getKettle();

  kettle.fill();

  const teaPromise = kettle
    .boil() // Assuming boil function returns a promise
    .catch((error) => {
      // Only handles kettle issues
      // Rethrows the error keep the rejected the promise state
      throw new Error("Kettle broke 😭");
    })
    .then(water => {
      kettle.pour(water, teapot);
      return waitMinutes(5); // Assuming waitMinutes returns a promise
    }).then(() => {
      const cup = getCup();
      const tea = teapot.pourInto(cup);
      return waitMinutes(5)
        .then(() => tea); // Our promise chain must return the cup of tea
    });

  addTeaLeaves(teapot);
  
  return teaPromise;
}
3 penangan dan memberi mereka panggilan balik yang kemudian menerima nilai secara asinkron. Karena callback seharusnya dijalankan satu per satu secara berurutan, kita bisa memasukkannya ke dalam antrian

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
7Menambahkan antrean internal yang mewakili penangan
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
4 dan
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
5 terlampir

Bagaimana ini akan bekerja

  1. Setiap kali
    new Promise((resolve, reject) => {
      setTimeout(() => resolve("done!"), 1000);
    });
    _4 atau
    new Promise((resolve, reject) => {
      setTimeout(() => resolve("done!"), 1000);
    });
    5 dipanggil, kami akan mendorong penangan panggilan balik ke antrean. Akan selalu ada penangan untuk keduanya, kecuali salah satu dari mereka hanya akan meneruskan nilai janji sebagai hasilnya, seperti ini
    import Futurable from "./Futurable";
    
    describe("Futurable <constructor>", () => {
      it(`returns a promise-like object,
          that resolves it's chain after invoking <resolve>`, (done) => {
        new Futurable<string>((resolve) => {
          setTimeout(() => {
            resolve("testing");
          }, 20);
        }).then((val) => {
          expect(val).toBe("testing");
          done();
        });
      });
    
      it("is always asynchronous", () => {
        let value = "no";
        new Futurable<string>((resolve) => {
          value = "yes;";
          resolve(value);
        });
        expect(value).toBe("no");
      });
    
      it("resolves with the returned value", (done) => {
        new Futurable<string>((resolve) => resolve("testing")).then((val) => {
          expect(val).toBe("testing");
          done();
        });
      });
    
      it("resolves a Futurable before calling <then>", (done) => {
        new Futurable<string>((resolve) =>
          resolve(new Futurable((resolve) => resolve("testing")))
        ).then((val) => {
          expect(val).toBe("testing");
          done();
        });
      });
    
      it("resolves a Futurable before calling <catch>", (done) => {
        new Futurable<string>((resolve) =>
          resolve(new Futurable((_, reject) => reject("fail")))
        ).catch((reason) => {
          expect(reason).toBe("fail");
          done();
        });
      });
    
      it("catches errors from <reject>", (done) => {
        const error = new Error("Why u fail?");
    
        new Futurable((_, reject) => {
          return reject(error);
        }).catch((err: Error) => {
          expect(err).toBe(error);
          done();
        });
      });
    
      it("catches errors from <throw>", (done) => {
        const error = new Error("Why u fail?");
    
        new Futurable(() => {
          throw error;
        }).catch((err) => {
          expect(err).toBe(error);
          done();
        });
      });
    
      it("does not change state anymore after promise is fulfilled", (done) => {
        new Futurable((resolve, reject) => {
          resolve("success");
          reject("fail");
        })
          .catch(() => {
            done.fail(new Error("Should not be called"));
          })
          .then((value) => {
            expect(value).toBe("success");
            done();
          });
      });
    
      it("does not change state anymore after promise is rejected", (done) => {
        new Futurable((resolve, reject) => {
          reject("fail");
          resolve("success");
        })
          .then(() => {
            done.fail(new Error("Should not be called"));
          })
          .catch((err) => {
            expect(err).toBe("fail");
            done();
          });
      });
    });
    
    describe("Futurable chaining", () => {
      it("resolves chained <then>", (done) => {
        new Futurable<number>((resolve) => {
          resolve(0);
        })
          .then((value) => value + 1)
          .then((value) => value + 1)
          .then((value) => value + 1)
          .then((value) => {
            expect(value).toBe(3);
            done();
          });
      });
    
      it("resolves <then> chain after <catch>", (done) => {
        new Futurable<number>(() => {
          throw new Error("Why u fail?");
        })
          .catch(() => {
            return "testing";
          })
          .then((value) => {
            expect(value).toBe("testing");
            done();
          });
      });
    
      it("catches errors thrown in <then>", (done) => {
        const error = new Error("Why u fail?");
    
        new Futurable((resolve) => {
          resolve();
        })
          .then(() => {
            throw error;
          })
          .catch((err) => {
            expect(err).toBe(error);
            done();
          });
      });
    
      it("catches errors thrown in <catch>", (done) => {
        const error = new Error("Final error");
    
        new Futurable((_, reject) => {
          reject(new Error("Initial error"));
        })
          .catch(() => {
            throw error;
          })
          .catch((err) => {
            expect(err).toBe(error);
            done();
          });
      });
    
      it("short-circuits <then> chain on error", (done) => {
        const error = new Error("Why u fail?");
    
        new Futurable(() => {
          throw error;
        })
          .then(() => {
            done.fail(new Error("Should not be called"));
          })
          .catch((err) => {
            expect(err).toBe(error);
            done();
          });
      });
    
      it("passes value through undefined <then>", (done) => {
        new Futurable((resolve) => {
          resolve("testing");
        })
          .then()
          .then((value) => {
            expect(value).toBe("testing");
            done();
          });
      });
    
      it("passes value through undefined <catch>", (done) => {
        const error = new Error("Why u fail?");
    
        new Futurable((_, reject) => {
          reject(error);
        })
          .catch()
          .catch((err) => {
            expect(err).toBe(error);
            done();
          });
      });
    });
    
    describe("Futurable <finally>", () => {
      it("it is called when Futurable is resolved", (done) => {
        new Futurable((resolve) => resolve("success")).finally(() => {
          done();
        });
      });
    
      it("it is called when Futurable is rejected", (done) => {
        new Futurable((_, reject) => reject("fail")).finally(() => {
          done();
        });
      });
    
      it("it preserves a resolved promise state", (done) => {
        let finallyCalledTimes = 0;
    
        new Futurable((resolve) => resolve("success"))
          .finally(() => {
            finallyCalledTimes += 1;
          })
          .then((value) => {
            expect(value).toBe("success");
            expect(finallyCalledTimes).toBe(1);
            done();
          });
      });
    
      it("it preserves a rejected promise state", (done) => {
        let finallyCalledTimes = 0;
    
        new Futurable((_, reject) => reject("fail"))
          .finally(() => {
            finallyCalledTimes += 1;
          })
          .catch((reason) => {
            expect(reason).toBe("fail");
            expect(finallyCalledTimes).toBe(1);
            done();
          });
      });
    });
    7. Ini akan memastikan, bahwa seluruh rantai penangan dieksekusi, tetapi penangan yang tidak relevan "dilewati", mis. g. lewati semua
    new Promise((resolve, reject) => {
      setTimeout(() => resolve("done!"), 1000);
    });
    _5 panggilan jika tidak ada kesalahan
  2. Kami akan mengeksekusi semua penangan saat janji dipenuhi atau ditolak
  3. Kami akan mengeksekusi penyelesai segera setelah penangan didorong ke antrean, jika penangan dilampirkan ke janji yang telah diselesaikan. Namun, kita harus ingat untuk tidak mengeksekusi penangan sampai janji telah diselesaikan
  4. Kami akan menghapus semua penangan segera setelah mereka selesai mengeksekusi

Mari tulis fungsi untuk mengeksekusi handler terlebih dahulu

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
_8A fungsi untuk mengeksekusi
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
4 dan
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
5 callback handler

Pertama, kita periksa apakah promise sudah terselesaikan, jika belum kita lewati. Kami hanya ingin mengeksekusi penangan

new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
_4 dan
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
5 hanya pada janji yang diselesaikan

Kemudian kami menjalankan semua penangan untuk

new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
_4 atau
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
5 tergantung pada apakah janji diselesaikan atau ditolak. Setelah itu, kami membersihkan semua penangan agar tidak dieksekusi lagi

Terakhir, kita perlu menjalankan resolver segera setelah kita menetapkan hasil dari promise, yang akan menjalankan rantai promise

7) Menerapkan. kemudian

Kita dapat menjalankan penangan, tetapi kita masih memerlukan cara untuk mendorong penangan ke antrean. Di situlah

new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
4 penangan masuk

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
_9Mendorong penangan ke dalam antrian

Kodenya terlihat menakutkan, tetapi sebenarnya cukup sederhana saat dipecah

Menurut spesifikasi,

new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
4 seharusnya dapat menangani panggilan balik pemenuhan dan penolakan, dan jenisnya berasal langsung dari antarmuka ES6
const promise = new Promise((resolve, reject) => {
  resolve(1);
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});
5. Pawang harus selalu mengembalikan janji sehingga mereka dapat dirantai, sehingga sebagai hasilnya, kami mengembalikan
class Futurable<T> {
  constructor(
    executor: (
      resolve: (value: T | Futurable<T>) => void,
      reject: (reason?: any) => void
    ) => void
  ) {
    // TODO: implement
  }

  then = <R1 = T, R2 = never>(
    onFulfilled?: (value: T) => R1 | Futurable<R1>,
    onRejected?: (reason: any) => R2 | Futurable<R2>
  ) => {
    // TODO: implement
  };

  catch = <R = never>(onRejected?: (reason: any) => R | Futurable<R>) => {
    // TODO: implement
  };

  finally = (onFinally: () => void): Futurable<T> => {
    // TODO: implement
  };
}

export default Futurable<T>;
8

Kami mendorong

new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
4 dan
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
5 penangan, yang pada dasarnya identik, kecuali satu memanggil panggilan balik penyelesaian, dan yang lainnya - tolak

Kami menyelesaikan janji dengan hasil yang dikembalikan dari panggilan balik. Jika panggilan balik tidak diberikan, kami cukup melewati nilai asli dari janji, yang memungkinkan kami untuk merangkai janji bahkan jika kami menghilangkan salah satu penangan, pada dasarnya menggantinya dengan

import Futurable from "./Futurable";

describe("Futurable <constructor>", () => {
  it(`returns a promise-like object,
      that resolves it's chain after invoking <resolve>`, (done) => {
    new Futurable<string>((resolve) => {
      setTimeout(() => {
        resolve("testing");
      }, 20);
    }).then((val) => {
      expect(val).toBe("testing");
      done();
    });
  });

  it("is always asynchronous", () => {
    let value = "no";
    new Futurable<string>((resolve) => {
      value = "yes;";
      resolve(value);
    });
    expect(value).toBe("no");
  });

  it("resolves with the returned value", (done) => {
    new Futurable<string>((resolve) => resolve("testing")).then((val) => {
      expect(val).toBe("testing");
      done();
    });
  });

  it("resolves a Futurable before calling <then>", (done) => {
    new Futurable<string>((resolve) =>
      resolve(new Futurable((resolve) => resolve("testing")))
    ).then((val) => {
      expect(val).toBe("testing");
      done();
    });
  });

  it("resolves a Futurable before calling <catch>", (done) => {
    new Futurable<string>((resolve) =>
      resolve(new Futurable((_, reject) => reject("fail")))
    ).catch((reason) => {
      expect(reason).toBe("fail");
      done();
    });
  });

  it("catches errors from <reject>", (done) => {
    const error = new Error("Why u fail?");

    new Futurable((_, reject) => {
      return reject(error);
    }).catch((err: Error) => {
      expect(err).toBe(error);
      done();
    });
  });

  it("catches errors from <throw>", (done) => {
    const error = new Error("Why u fail?");

    new Futurable(() => {
      throw error;
    }).catch((err) => {
      expect(err).toBe(error);
      done();
    });
  });

  it("does not change state anymore after promise is fulfilled", (done) => {
    new Futurable((resolve, reject) => {
      resolve("success");
      reject("fail");
    })
      .catch(() => {
        done.fail(new Error("Should not be called"));
      })
      .then((value) => {
        expect(value).toBe("success");
        done();
      });
  });

  it("does not change state anymore after promise is rejected", (done) => {
    new Futurable((resolve, reject) => {
      reject("fail");
      resolve("success");
    })
      .then(() => {
        done.fail(new Error("Should not be called"));
      })
      .catch((err) => {
        expect(err).toBe("fail");
        done();
      });
  });
});

describe("Futurable chaining", () => {
  it("resolves chained <then>", (done) => {
    new Futurable<number>((resolve) => {
      resolve(0);
    })
      .then((value) => value + 1)
      .then((value) => value + 1)
      .then((value) => value + 1)
      .then((value) => {
        expect(value).toBe(3);
        done();
      });
  });

  it("resolves <then> chain after <catch>", (done) => {
    new Futurable<number>(() => {
      throw new Error("Why u fail?");
    })
      .catch(() => {
        return "testing";
      })
      .then((value) => {
        expect(value).toBe("testing");
        done();
      });
  });

  it("catches errors thrown in <then>", (done) => {
    const error = new Error("Why u fail?");

    new Futurable((resolve) => {
      resolve();
    })
      .then(() => {
        throw error;
      })
      .catch((err) => {
        expect(err).toBe(error);
        done();
      });
  });

  it("catches errors thrown in <catch>", (done) => {
    const error = new Error("Final error");

    new Futurable((_, reject) => {
      reject(new Error("Initial error"));
    })
      .catch(() => {
        throw error;
      })
      .catch((err) => {
        expect(err).toBe(error);
        done();
      });
  });

  it("short-circuits <then> chain on error", (done) => {
    const error = new Error("Why u fail?");

    new Futurable(() => {
      throw error;
    })
      .then(() => {
        done.fail(new Error("Should not be called"));
      })
      .catch((err) => {
        expect(err).toBe(error);
        done();
      });
  });

  it("passes value through undefined <then>", (done) => {
    new Futurable((resolve) => {
      resolve("testing");
    })
      .then()
      .then((value) => {
        expect(value).toBe("testing");
        done();
      });
  });

  it("passes value through undefined <catch>", (done) => {
    const error = new Error("Why u fail?");

    new Futurable((_, reject) => {
      reject(error);
    })
      .catch()
      .catch((err) => {
        expect(err).toBe(error);
        done();
      });
  });
});

describe("Futurable <finally>", () => {
  it("it is called when Futurable is resolved", (done) => {
    new Futurable((resolve) => resolve("success")).finally(() => {
      done();
    });
  });

  it("it is called when Futurable is rejected", (done) => {
    new Futurable((_, reject) => reject("fail")).finally(() => {
      done();
    });
  });

  it("it preserves a resolved promise state", (done) => {
    let finallyCalledTimes = 0;

    new Futurable((resolve) => resolve("success"))
      .finally(() => {
        finallyCalledTimes += 1;
      })
      .then((value) => {
        expect(value).toBe("success");
        expect(finallyCalledTimes).toBe(1);
        done();
      });
  });

  it("it preserves a rejected promise state", (done) => {
    let finallyCalledTimes = 0;

    new Futurable((_, reject) => reject("fail"))
      .finally(() => {
        finallyCalledTimes += 1;
      })
      .catch((reason) => {
        expect(reason).toBe("fail");
        expect(finallyCalledTimes).toBe(1);
        done();
      });
  });
});
7

Kemudian kami membungkus semuanya dalam try/catch, karena kami harus dapat menolak janji dengan melemparkan kesalahan pada handler, sebagai alternatif untuk memanggil

const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
8

Terakhir, seperti yang disebutkan sebelumnya, kita perlu mengeksekusi penyelesai setelah menambahkan mendorong janji ke dalam antrean, sehingga jika janji sudah diselesaikan, penangan baru akan tetap berjalan

Mari kita uji

const promise = new Promise((resolve, reject) => {
  // This function runs an asyncronous operation that will call:
  // resolve(value) - if it succeeded, with the resolved value;
  // reject(reason) - if it failed, with the error set as value;
});
0Menguji
const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
0 janji kami

Jika Anda menjalankan kode ini, Anda akan melihat

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
04 dan
function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
05, di layar, yang berarti bahwa janji
const promise = new Promise((resolve, reject) => {
  // handle async code
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)
0 kami dapat dirantai dan dapat menangani pemenuhan dan penolakan

8) Menerapkan. menangkap dan. akhirnya

Kita sudah memiliki Promise yang berfungsi penuh sekarang, tapi mari kita tambahkan sedikit gula.

new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
5 dan
function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
08

Dari tes di atas, Anda dapat melihat, bahwa

function makeTea() {
  const teapot = getTeapot();
  const kettle = getKettle();

  kettle.fill();

  const teaPromise = kettle
    .boil() // Assuming boil function returns a promise
    .catch((error) => {
      // Only handles kettle issues
      // Rethrows the error keep the rejected the promise state
      throw new Error("Kettle broke 😭");
    })
    .then(water => {
      kettle.pour(water, teapot);
      return waitMinutes(5); // Assuming waitMinutes returns a promise
    }).then(() => {
      const cup = getCup();
      const tea = teapot.pourInto(cup);
      return waitMinutes(5)
        .then(() => tea); // Our promise chain must return the cup of tea
    });

  addTeaLeaves(teapot);
  
  return teaPromise;
}
_3 setara dengan
const promise = new Promise((resolve, reject) => {
  resolve(1);
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});

promise.then((result) => {
  console.log(result); // 1
  return result + 1;
});
4 tanpa
function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
11 handler

const promise = new Promise((resolve, reject) => {
  // This function runs an asyncronous operation that will call:
  // resolve(value) - if it succeeded, with the resolved value;
  // reject(reason) - if it failed, with the error set as value;
});
1Kode untuk
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
5 handler

Dan

new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
_6 sesederhana mengeksekusi panggilan balik tidak peduli apakah janji itu dipenuhi atau ditolak

const promise = new Promise((resolve, reject) => {
  // This function runs an asyncronous operation that will call:
  // resolve(value) - if it succeeded, with the resolved value;
  // reject(reason) - if it failed, with the error set as value;
});
2Kode untuk
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
6 handler

Satu-satunya peringatan adalah bahwa

new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
_6 tidak boleh mengubah status janji, sehingga melewati apa pun hasilnya - mengembalikan nilai jika janji diselesaikan atau mengembalikan kesalahan jika ditolak

Anda dapat melihat implementasi lengkapnya di sini

Itu dia - implementasi premis dasar yang kami buat sendiri. Sebagai eksperimen yang menyenangkan, coba gunakan secara bergantian dengan janji asli dan lihat apakah berhasil. Jika Anda ingin menyempurnakan implementasi lebih jauh dan memperdalam pemahaman tentang promise, coba terapkan metode statis ini

  • function makeTea(done) {
      const teapot = getTeapot();
      const kettle = getKettle();
        
      kettle.fill();
    
      kettle.boil((error, water) => {
        if (error) {
          done(new Error('Kettle broke 😭'));
        } else { 
          kettle.pour(water, teapot);
          waitMinutes(5, () => {
            const cup = getCup();
            const tea = teapot.pourInto(cup);
            waitMinutes(5, () => {
                done(tea)
            });
          });
        }
      });
    
      // Executed right after putting on a kettle:
      addTeaLeaves(teapot);
    }
    _16
  • function makeTea(done) {
      const teapot = getTeapot();
      const kettle = getKettle();
        
      kettle.fill();
    
      kettle.boil((error, water) => {
        if (error) {
          done(new Error('Kettle broke 😭'));
        } else { 
          kettle.pour(water, teapot);
          waitMinutes(5, () => {
            const cup = getCup();
            const tea = teapot.pourInto(cup);
            waitMinutes(5, () => {
                done(tea)
            });
          });
        }
      });
    
      // Executed right after putting on a kettle:
      addTeaLeaves(teapot);
    }
    _17
  • function makeTea(done) {
      const teapot = getTeapot();
      const kettle = getKettle();
        
      kettle.fill();
    
      kettle.boil((error, water) => {
        if (error) {
          done(new Error('Kettle broke 😭'));
        } else { 
          kettle.pour(water, teapot);
          waitMinutes(5, () => {
            const cup = getCup();
            const tea = teapot.pourInto(cup);
            waitMinutes(5, () => {
                done(tea)
            });
          });
        }
      });
    
      // Executed right after putting on a kettle:
      addTeaLeaves(teapot);
    }
    _18
  • function makeTea(done) {
      const teapot = getTeapot();
      const kettle = getKettle();
        
      kettle.fill();
    
      kettle.boil((error, water) => {
        if (error) {
          done(new Error('Kettle broke 😭'));
        } else { 
          kettle.pour(water, teapot);
          waitMinutes(5, () => {
            const cup = getCup();
            const tea = teapot.pourInto(cup);
            waitMinutes(5, () => {
                done(tea)
            });
          });
        }
      });
    
      // Executed right after putting on a kettle:
      addTeaLeaves(teapot);
    }
    _19
  • function makeTea(done) {
      const teapot = getTeapot();
      const kettle = getKettle();
        
      kettle.fill();
    
      kettle.boil((error, water) => {
        if (error) {
          done(new Error('Kettle broke 😭'));
        } else { 
          kettle.pour(water, teapot);
          waitMinutes(5, () => {
            const cup = getCup();
            const tea = teapot.pourInto(cup);
            waitMinutes(5, () => {
                done(tea)
            });
          });
        }
      });
    
      // Executed right after putting on a kettle:
      addTeaLeaves(teapot);
    }
    _20
  • function makeTea(done) {
      const teapot = getTeapot();
      const kettle = getKettle();
        
      kettle.fill();
    
      kettle.boil((error, water) => {
        if (error) {
          done(new Error('Kettle broke 😭'));
        } else { 
          kettle.pour(water, teapot);
          waitMinutes(5, () => {
            const cup = getCup();
            const tea = teapot.pourInto(cup);
            waitMinutes(5, () => {
                done(tea)
            });
          });
        }
      });
    
      // Executed right after putting on a kettle:
      addTeaLeaves(teapot);
    }
    _21 jika Anda benar-benar ingin maju

Asinkron/menunggu

Sekarang Anda mungkin telah mengetahui bahwa janji-janji itu menyelesaikan beberapa masalah bekerja dengan operasi asinkron dalam JavaScript, namun itu tidak sempurna

  1. Mereka tidak sepenuhnya menghilangkan sarang;
  2. Keterbacaannya tidak bagus;
  3. Meskipun janji dapat dikembalikan secara sinkron, hasilnya tidak dapat diakses secara sinkron;
  4. Konstruksi bahasa reguler tidak mudah digunakan (mis. g. blok coba/tangkap)

Nah, baru-baru ini cara yang lebih nyaman untuk menangani operasi asinkron diperkenalkan - async/menunggu, dan janji adalah intinya

Fungsi asinkron

Saat mendeklarasikan suatu fungsi, kita dapat menandainya sebagai async, yang memungkinkan kita mengembalikan nilai apa pun yang sama seperti biasanya, tetapi hasilnya secara otomatis akan dianggap sebagai janji

const promise = new Promise((resolve, reject) => {
  // This function runs an asyncronous operation that will call:
  // resolve(value) - if it succeeded, with the resolved value;
  // reject(reason) - if it failed, with the error set as value;
});
3Contoh penggunaan fungsi async di JavaScript

Seperti yang kita lihat di contoh, mengembalikan nilai sama dengan menyelesaikan janji. Sekalipun tidak ada nilai yang dikembalikan, hasilnya tetap janji, kecuali dengan

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
22 sebagai hasilnya. Demikian pula, melempar kesalahan sama dengan menolak janji

const promise = new Promise((resolve, reject) => {
  // This function runs an asyncronous operation that will call:
  // resolve(value) - if it succeeded, with the resolved value;
  // reject(reason) - if it failed, with the error set as value;
});
4Contoh menolak janji yang dibuat oleh fungsi async di JavaScript

Menunggu

Apa yang keren tentang fungsi async, apakah di dalamnya Anda dapat menggunakan kata kunci

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
23 untuk menyelesaikan janji secara sinkron

const promise = new Promise((resolve, reject) => {
  // This function runs an asyncronous operation that will call:
  // resolve(value) - if it succeeded, with the resolved value;
  // reject(reason) - if it failed, with the error set as value;
});
5Contoh menunggu janji di JavaScript

Kata kunci

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
23 membuat pemblokiran kode sampai janji diselesaikan, pada dasarnya menunggu operasi selesai sampai dilanjutkan dengan eksekusi kode. Ini memungkinkan kita mendapatkan hasil dari fungsi asinkron dengan cara yang hampir sama seperti fungsi biasa

Tapi bukan itu. Saat Anda menggunakan

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
23 kesalahan juga akan dilemparkan secara serempak, sehingga blok coba/tangkap biasa akan berfungsi

const promise = new Promise((resolve, reject) => {
  // This function runs an asyncronous operation that will call:
  // resolve(value) - if it succeeded, with the resolved value;
  // reject(reason) - if it failed, with the error set as value;
});
6Contoh penanganan error yang dilakukan oleh fungsi async di JavaScript

Namun, pada akhirnya, itu semua hanya janji, jadi async/menunggu dapat digunakan dengan

new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
4,
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
5 dan
new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
6 secara bergantian

const promise = new Promise((resolve, reject) => {
  // This function runs an asyncronous operation that will call:
  // resolve(value) - if it succeeded, with the resolved value;
  // reject(reason) - if it failed, with the error set as value;
});
7Contoh janji yang digunakan secara bergantian dengan async/menunggu di JavaScript

Sebagai latihan terakhir, mari tulis ulang fungsi persiapan teh kita menggunakan async/await

const promise = new Promise((resolve, reject) => {
  // This function runs an asyncronous operation that will call:
  // resolve(value) - if it succeeded, with the resolved value;
  // reject(reason) - if it failed, with the error set as value;
});
8Contoh persiapan teh menggunakan async/menunggu dan janji

Seperti yang Anda lihat, async/await telah menghilangkan sarang yang berlebihan dan umumnya lebih mudah dibaca dan dipahami

Peringatan

Seperti yang dapat Anda bayangkan, async/await menyelesaikan sebagian besar masalah yang tersisa dengan promise. Namun, ada beberapa kesalahan umum yang dialami pemula

Anda tidak dapat menggunakan

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
23 di tingkat root atau fungsi biasa, hanya di fungsi asinkron

const promise = new Promise((resolve, reject) => {
  // This function runs an asyncronous operation that will call:
  // resolve(value) - if it succeeded, with the resolved value;
  // reject(reason) - if it failed, with the error set as value;
});
9Menggunakan menunggu secara tidak benar

Meskipun, beberapa runtime JavaScript modern (browser modern, versi simpul terbaru) memungkinkan

function makeTea(done) {
  const teapot = getTeapot();
  const kettle = getKettle();
    
  kettle.fill();

  kettle.boil((error, water) => {
    if (error) {
      done(new Error('Kettle broke 😭'));
    } else { 
      kettle.pour(water, teapot);
      waitMinutes(5, () => {
        const cup = getCup();
        const tea = teapot.pourInto(cup);
        waitMinutes(5, () => {
            done(tea)
        });
      });
    }
  });

  // Executed right after putting on a kettle:
  addTeaLeaves(teapot);
}
30 di tingkat root. Sebagai solusi untuk runtime JS yang lebih lama, gabungkan panggilan dalam fungsi async anonim yang memanggil sendiri

new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
0Fungsi asinkron anonim yang memanggil sendiri

Pikiran terakhir

JavaScript modern menyediakan cara untuk bekerja dengan operasi asinkron dengan mudah. Namun, promise adalah dasar dari segala sesuatu yang asinkron dalam JavaScript, jadi penting untuk memahaminya sepenuhnya

Saya telah menunjukkan bagaimana Anda akan mengimplementasikan janji Anda sendiri dan mudah-mudahan, proses tersebut telah menunjukkan kepada Anda bahwa tidak ada keajaiban yang terlibat saat Anda melanggarnya

Jika ini adalah keterlibatan pertama Anda dengan janji, Anda mungkin akan sedikit kewalahan, tetapi jangan khawatir, dengan latihan, menggunakan janji akan menjadi kebiasaan, dan Anda selalu dapat menggunakan posting ini sebagai referensi jika Anda ingin meninjau kembali penerapannya

Bagaimana Janji semua bekerja secara internal?

Janji. all() metode statis mengambil iterable janji sebagai masukan dan mengembalikan satu Janji . Janji yang dikembalikan ini terpenuhi ketika semua janji masukan terpenuhi (termasuk ketika iterable kosong dilewatkan), dengan larik nilai pemenuhan.

Bagaimana Promise bekerja di JavaScript di balik terpal?

Itu mengeksekusi panggilan balik dari janji segera. Itu menyusun fungsi yang mengambil nilai Janji masa depan dan mengubahnya untuk mengembalikan Janji baru yang berisi nilai yang diubah. jika Anda mengembalikan Janji dalam a. lalu metode, itu akan meratakannya untuk menghindari Janji bersarang

Bagaimana Janji dijalankan dalam JavaScript?

Hanya untuk meninjau, janji dapat dibuat dengan sintaks konstruktor, seperti ini. let promise = new Promise(function(resolve, reject) { // Kode untuk mengeksekusi }); Fungsi konstruktor mengambil fungsi sebagai argumen. Fungsi ini disebut fungsi pelaksana.

Apa yang sebenarnya dipecahkan oleh Janji dalam JavaScript?

Anda dapat membaca lebih lanjut tentang sintaks async / await dalam fungsi async dan referensi await. Promise memecahkan cacat mendasar dengan callback piramida malapetaka, dengan menangkap semua kesalahan, bahkan melontarkan pengecualian dan kesalahan pemrograman . Ini penting untuk komposisi fungsional dari operasi asinkron.