Promise

想像有一位超級歌星,粉絲日以繼夜關心專輯什麼時候出,這位超級歌星很煩惱,因此想出了一個辦法,讓粉絲追蹤他的 IG ,若有任何關於他的消息都可以第一時間知道。 在編寫程式碼也會遇到一樣的問題: 1. 生產程式碼:作一些事需要時間,像是加載腳本,腳色是歌手。 2. 消費程式碼:知道生產程式碼的結果,不論有沒有成功,腳色是粉絲。 3. promise 是連接 2 者的橋樑,生產程式碼的結果會保證傳到每個消費程式碼,腳色是 IG。

// syntax,executor 是生產程式碼,最終會產生結果,傳到 promise,promise 物件有 2 個內部屬性,
// state 描述 promise 狀態,一開始 pedding => fullfilled or rejected
// result 任意值,一開始 undefined => value or error
let promise = new Promise(function(resolve, reject) {
  // executor (生产者代码,"singer")
});
// 會自動呼叫 executor,executor 會接收 resolve、reject 2 個參數。
let promise = new Promise(function(resolve, reject) {
  // the function is executed automatically when the promise is constructed

  // after 1 second signal that the job is done with the result "done"
  setTimeout(() => resolve("done"), 1000);
});
let promise = new Promise(function(resolve, reject) {
  // after 1 second signal that the job is finished with an error
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// executor 只會有一個結果,另一個結果會被忽略。
let promise = new Promise(function(resolve, reject) {
  resolve("done");

  reject(new Error("…")); // 被忽略
  setTimeout(() => resolve("…")); // 被忽略
});

// 可以使用任何類型的參數帶入 reject,但建議使用 Error 物件

// 通常 executor 會執行一些異步程式,這時需要一些時間才能得到結果。我們可以立即返回結果,當任務
// 已經完成。
let promise = new Promise(function(resolve, reject) {
  // not taking our time to do the job
  resolve(123); // immediately give the result: 123
});

// promise 的內建屬性 state and result,不能從消費程式碼取得,但可以透過
// .then/.catch/.finally 等方法取得

Consumers: then, catch, finally

消費程式碼可以得知生產結果,透過 .then, .catch and .finally等方法。

then

// syntax,成功時執行第 1 個參數,失敗時執行第 2 個參數
promise.then(
  function(result) { /* handle a successful result */ },
  function(error) { /* handle an error */ }
);

// 成功
let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("done!"), 1000);
});

// resolve runs the first function in .then
promise.then(
  result => alert(result), // shows "done!" after 1 second
  error => alert(error) // doesn't run
);

// 失敗
let promise = new Promise(function(resolve, reject) {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

// reject runs the second function in .then
promise.then(
  result => alert(result), // doesn't run
  error => alert(error) // shows "Error: Whoops!" after 1 second
);

// 只返回成功結果
let promise = new Promise(resolve => {
  setTimeout(() => resolve("done!"), 1000);
});

promise.then(alert); // shows "done!" after 1 second

catch

// 只返回失敗結果,可以用 .then(null, errorHandlingFunction) 或 .catch(errorHandlingFunction)
// .catch(f) 是 .then(null, f) 的簡寫
let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

// .catch(f) is the same as promise.then(null, f)
promise.catch(alert); // shows "Error: Whoops!" after 1 second

finally

// 就像 try {...} catch {...} 有 finally,promise 也有 finally
// .finally(f) 很像 .then(f, f),當 promise 完成他會被執行無論成功或失敗。
// finally 適合使用在清空記憶體
new Promise((resolve, reject) => {
  /* do something that takes time, and then call resolve/reject */
})
  // runs when the promise is settled, doesn't matter successfully or not
  .finally(() => stop loading indicator)
  .then(result => show result, err => show error)
  
// .finally(f) 跟 .then(f, f) 的差別
// 1. finally 沒有參數
// 2. finally 傳遞 result 參數
// 成功
new Promise((resolve, reject) => {
  setTimeout(() => resolve("result"), 2000)
})
  .finally(() => alert("Promise ready"))
  .then(result => alert(result)); // <-- .then handles the result

// 失敗
new Promise((resolve, reject) => {
  throw new Error("error");
})
  .finally(() => alert("Promise ready"))
  .catch(err => alert(err));  // <-- .catch handles the error object
 
// 3. .finally(f) 比 .then(f, f) 簡潔

// 當 promise 執行完畢結果會立即回傳
// an immediately resolved promise
let promise = new Promise(resolve => resolve("done!"));

promise.then(alert); // done! (shows up right now)

Example: loadScript

function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;

  script.onload = () => callback(null, script);
  script.onerror = () => callback(new Error(`Script load error for ${src}`));

  document.head.append(script);
}

// 重新改寫
function loadScript(src) {
  return new Promise(function(resolve, reject) {
    let script = document.createElement('script');
    script.src = src;

    script.onload = () => resolve(script);
    script.onerror = () => reject(new Error(`Script load error for ${src}`));

    document.head.append(script);
  });
}

// 使用
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");

promise.then(
  script => alert(`${script.src} is loaded!`),
  error => alert(`Error: ${error.message}`)
);

promise.then(script => alert('One more handler to do something else!'));

Promises

Callbacks

Promises allow us to do things in the natural order. First, we run loadScript(script), and .then we write what to do with the result.

We must have a callback function at our disposal when calling loadScript(script, callback). In other words, we must know what to do with the result before loadScript is called.

We can call .then on a Promise as many times as we want. Each time, we’re adding a new “fan”, a new subscribing function, to the “subscription list”. More about this in the next chapter: Promises chaining.

There can be only one callback.

Last updated