99re热视频这里只精品,久久久天堂国产精品女人,国产av一区二区三区,久久久精品成人免费看片,99久久精品免费看国产一区二区三区

Javascript Promise

2023-02-17 10:53 更新

想象一下,你是一位頂尖歌手,粉絲沒日沒夜地詢問你下首歌什么時候發(fā)。

為了從中解放,你承諾(promise)會在單曲發(fā)布的第一時間發(fā)給他們。你給了粉絲們一個列表。他們可以在上面填寫他們的電子郵件地址,以便當歌曲發(fā)布后,讓所有訂閱了的人能夠立即收到。即便遇到不測,例如錄音室發(fā)生了火災,以致你無法發(fā)布新歌,他們也能及時收到相關通知。

每個人都很開心:你不會被任何人催促,粉絲們也不用擔心錯過歌曲發(fā)行。

這是我們在編程中經(jīng)常遇到的事兒與真實生活的類比:

  1. “生產者代碼(producing code)”會做一些事兒,并且會需要一些時間。例如,通過網(wǎng)絡加載數(shù)據(jù)的代碼。它就像一位“歌手”。
  2. “消費者代碼(consuming code)”想要在“生產者代碼”完成工作的第一時間就能獲得其工作成果。許多函數(shù)可能都需要這個結果。這些就是“粉絲”。
  3. Promise 是將“生產者代碼”和“消費者代碼”連接在一起的一個特殊的 JavaScript 對象。用我們的類比來說:這就是就像是“訂閱列表”。“生產者代碼”花費它所需的任意長度時間來產出所承諾的結果,而 “promise” 將在它(譯注:指的是“生產者代碼”,也就是下文所說的 executor)準備好時,將結果向所有訂閱了的代碼開放。

這種類比并不十分準確,因為 JavaScipt 的 promise 比簡單的訂閱列表更加復雜:它們還擁有其他的功能和局限性。但以此開始挺好的。

Promise 對象的構造器(constructor)語法如下:

let promise = new Promise(function(resolve, reject) {
  // executor(生產者代碼,“歌手”)
});

傳遞給 new Promise 的函數(shù)被稱為 executor。當 new Promise 被創(chuàng)建,executor 會自動運行。它包含最終應產出結果的生產者代碼。按照上面的類比:executor 就是“歌手”。

它的參數(shù) resolve 和 reject 是由 JavaScript 自身提供的回調。我們的代碼僅在 executor 的內部。

當 executor 獲得了結果,無論是早還是晚都沒關系,它應該調用以下回調之一:

  • ?resolve(value)? —— 如果任務成功完成并帶有結果 ?value?。
  • ?reject(error)? —— 如果出現(xiàn)了 error,?error? 即為 error 對象。

所以總結一下就是:executor 會自動運行并嘗試執(zhí)行一項工作。嘗試結束后,如果成功則調用 resolve,如果出現(xiàn) error 則調用 reject。

由 new Promise 構造器返回的 promise 對象具有以下內部屬性:

  • ?state? —— 最初是 ?"pending"?,然后在 ?resolve? 被調用時變?yōu)?nbsp;?"fulfilled"?,或者在 ?reject? 被調用時變?yōu)?nbsp;?"rejected"?。
  • ?result? —— 最初是 ?undefined?,然后在 ?resolve(value)? 被調用時變?yōu)?nbsp;?value?,或者在 ?reject(error)? 被調用時變?yōu)?nbsp;?error?。

所以,executor 最終將 promise 移至以下狀態(tài)之一:


稍后我們將看到“粉絲”如何訂閱這些更改。

下面是一個 promise 構造器和一個簡單的 executor 函數(shù),該 executor 函數(shù)具有包含時間(即 setTimeout)的“生產者代碼”:

let promise = new Promise(function(resolve, reject) {
  // 當 promise 被構造完成時,自動執(zhí)行此函數(shù)

  // 1 秒后發(fā)出工作已經(jīng)被完成的信號,并帶有結果 "done"
  setTimeout(() => resolve("done"), 1000);
});

通過運行上面的代碼,我們可以看到兩件事兒:

  1. executor 被自動且立即調用(通過 ?new Promise?)。
  2. executor 接受兩個參數(shù):?resolve? 和 ?reject?。這些函數(shù)由 JavaScript 引擎預先定義,因此我們不需要創(chuàng)建它們。我們只需要在準備好(譯注:指的是 executor 準備好)時調用其中之一即可。
  3. 經(jīng)過 1 秒的“處理”后,executor 調用 resolve("done") 來產生結果。這將改變 promise 對象的狀態(tài):


這是一個成功完成任務的例子,一個“成功實現(xiàn)了的諾言”。

下面則是一個 executor 以 error 拒絕 promise 的示例:

let promise = new Promise(function(resolve, reject) {
  // 1 秒后發(fā)出工作已經(jīng)被完成的信號,并帶有 error
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

對 reject(...) 的調用將 promise 對象的狀態(tài)移至 "rejected"


總而言之,executor 應該執(zhí)行一項工作(通常是需要花費一些時間的事兒),然后調用 resolve 或 reject 來改變對應的 promise 對象的狀態(tài)。

與最初的 “pending” promise 相反,一個 resolved 或 rejected 的 promise 都會被稱為 “settled”。

這只能有一個結果或一個 error

executor 只能調用一個 resolve 或一個 reject。任何狀態(tài)的更改都是最終的。

所有其他的再對 resolve 和 reject 的調用都會被忽略:

let promise = new Promise(function(resolve, reject) {
  resolve("done");

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

這的宗旨是,一個被 executor 完成的工作只能有一個結果或一個 error。

并且,resolve/reject 只需要一個參數(shù)(或不包含任何參數(shù)),并且將忽略額外的參數(shù)。

以 ?Error? 對象 reject

如果什么東西出了問題,executor 應該調用 reject。這可以使用任何類型的參數(shù)來完成(就像 resolve 一樣)。但建議使用 Error 對象(或繼承自 Error 的對象)。這樣做的理由很快就會顯而易見。

resolve/reject 可以立即進行

實際上,executor 通常是異步執(zhí)行某些操作,并在一段時間后調用 resolve/reject,但這不是必須的。我們還可以立即調用 resolve 或 reject,就像這樣:

let promise = new Promise(function(resolve, reject) {
  // 不花時間去做這項工作
  resolve(123); // 立即給出結果:123
});

例如,當我們開始做一個任務時,但隨后看到一切都已經(jīng)完成并已被緩存時,可能就會發(fā)生這種情況。

這挺好。我們立即就有了一個 resolved 的 promise。

?state? 和 ?result? 都是內部的

Promise 對象的 state 和 result 屬性都是內部的。我們無法直接訪問它們。但我們可以對它們使用 .then/.catch/.finally 方法。我們在下面對這些方法進行了描述。

消費者:then,catch

Promise 對象充當?shù)氖?executor(“生產者代碼”或“歌手”)和消費函數(shù)(“粉絲”)之間的連接,后者將接收結果或 error??梢酝ㄟ^使用 .then 和 .catch 方法注冊消費函數(shù)。

then

最重要最基礎的一個就是 ?.then?。

語法如下:

promise.then(
  function(result) { /* handle a successful result */ },
  function(error) { /* handle an error */ }
);

.then 的第一個參數(shù)是一個函數(shù),該函數(shù)將在 promise resolved 且接收到結果后執(zhí)行。

.then 的第二個參數(shù)也是一個函數(shù),該函數(shù)將在 promise rejected 且接收到 error 信息后執(zhí)行。

例如,以下是對成功 resolved 的 promise 做出的反應:

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

// resolve 運行 .then 中的第一個函數(shù)
promise.then(
  result => alert(result), // 1 秒后顯示 "done!"
  error => alert(error) // 不運行
);

第一個函數(shù)被運行了。

在 reject 的情況下,運行第二個:

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

// reject 運行 .then 中的第二個函數(shù)
promise.then(
  result => alert(result), // 不運行
  error => alert(error) // 1 秒后顯示 "Error: Whoops!"
);

如果我們只對成功完成的情況感興趣,那么我們可以只為 .then 提供一個函數(shù)參數(shù):

let promise = new Promise(resolve => {
  setTimeout(() => resolve("done!"), 1000);
});

promise.then(alert); // 1 秒后顯示 "done!"

catch

如果我們只對 error 感興趣,那么我們可以使用 null 作為第一個參數(shù):.then(null, errorHandlingFunction)。或者我們也可以使用 .catch(errorHandlingFunction),其實是一樣的:

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

// .catch(f) 與 promise.then(null, f) 一樣
promise.catch(alert); // 1 秒后顯示 "Error: Whoops!"

.catch(f) 調用是 .then(null, f) 的完全的模擬,它只是一個簡寫形式。

清理:finally

就像常規(guī) try {...} catch {...} 中的 finally 子句一樣,promise 中也有 finally

調用 .finally(f) 類似于 .then(f, f),因為當 promise settled 時 f 就會執(zhí)行:無論 promise 被 resolve 還是 reject。

finally 的功能是設置一個處理程序在前面的操作完成后,執(zhí)行清理/終結。

例如,停止加載指示器,關閉不再需要的連接等。

把它想象成派對的終結者。無論派對是好是壞,有多少朋友參加,我們都需要(或者至少應該)在它之后進行清理。

代碼可能看起來像這樣:

new Promise((resolve, reject) => {
  /* 做一些需要時間的事,之后調用可能會 resolve 也可能會 reject */
})
  // 在 promise 為 settled 時運行,無論成功與否
  .finally(() => stop loading indicator)
  // 所以,加載指示器(loading indicator)始終會在我們繼續(xù)之前停止
  .then(result => show result, err => show error)

請注意,finally(f) 并不完全是 then(f,f) 的別名。

它們之間有重要的區(qū)別:

  1. ?finally? 處理程序(handler)沒有參數(shù)。在 ?finally? 中,我們不知道 promise 是否成功。沒關系,因為我們的任務通常是執(zhí)行“常規(guī)”的完成程序(finalizing procedures)。
  2. 請看上面的例子:如你所見,?finally? 處理程序沒有參數(shù),promise 的結果由下一個處理程序處理。

  3. ?finally? 處理程序將結果或 error “傳遞”給下一個合適的處理程序。
  4. 例如,在這結果被從 finally 傳遞給了 then

    new Promise((resolve, reject) => {
      setTimeout(() => resolve("value"), 2000)
    })
      .finally(() => alert("Promise ready")) // 先觸發(fā)
      .then(result => alert(result)); // <-- .then 顯示 "value"

    正如我們所看到的,第一個 promise 返回的 value 通過 finally 被傳遞給了下一個 then。

    這非常方便,因為 finally 并不意味著處理一個 promise 的結果。如前所述,無論結果是什么,它都是進行常規(guī)清理的地方。

    下面是一個 promise 返回結果為 error 的示例,讓我們看看它是如何通過 finally 被傳遞給 catch 的:

    new Promise((resolve, reject) => {
      throw new Error("error");
    })
      .finally(() => alert("Promise ready")) // 先觸發(fā)
      .catch(err => alert(err));  // <-- .catch 顯示這個 error
  5. ?finally? 處理程序也不應該返回任何內容。如果它返回了,返回的值會默認被忽略。
  6. 此規(guī)則的唯一例外是當 ?finally? 處理程序拋出 error 時。此時這個 error(而不是任何之前的結果)會被轉到下一個處理程序。

總結:

  • ?finally? 處理程序沒有得到前一個處理程序的結果(它沒有參數(shù))。而這個結果被傳遞給了下一個合適的處理程序。
  • 如果 ?finally? 處理程序返回了一些內容,那么這些內容會被忽略。
  • 當 ?finally? 拋出 error 時,執(zhí)行將轉到最近的 error 的處理程序。

如果我們正確使用 ?finally?(將其用于常規(guī)清理),那么這些功能將很有用。

我們可以對 settled 的 promise 附加處理程序

如果 promise 為 pending 狀態(tài),.then/catch/finally 處理程序(handler)將等待它的結果。

有時候,當我們向一個 promise 添加處理程序時,它可能已經(jīng) settled 了。

在這種情況下,這些處理程序會立即執(zhí)行:

// 下面這 promise 在被創(chuàng)建后立即變?yōu)?resolved 狀態(tài)
let promise = new Promise(resolve => resolve("done!"));

promise.then(alert); // done!(現(xiàn)在顯示)

請注意這使得 promise 比現(xiàn)實生活中的“訂閱列表”方案強大得多。如果歌手已經(jīng)發(fā)布了他們的單曲,然后某個人在訂閱列表上進行了注冊,則他們很可能不會收到該單曲。實際生活中的訂閱必須在活動開始之前進行。

Promise 則更加靈活。我們可以隨時添加處理程序(handler):如果結果已經(jīng)在了,它們就會執(zhí)行。

示例:loadScript

接下來,讓我們看一下關于 promise 如何幫助我們編寫異步代碼的更多實際示例。

我們從上一章獲得了用于加載腳本的 loadScript 函數(shù)。

這是基于回調函數(shù)的變體,記住它:

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

讓我們用 promise 重寫它。

新函數(shù) loadScript 將不需要回調。取而代之的是,它將創(chuàng)建并返回一個在加載完成時 resolve 的 promise 對象。外部代碼可以使用 .then 向其添加處理程序(訂閱函數(shù)):

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('Another handler...'));

我們立刻就能發(fā)現(xiàn) promise 相較于基于回調的模式的一些好處:

promise callback
promise 允許我們按照自然順序進行編碼。首先,我們運行 loadScript 和 .then 來處理結果。 在調用 loadScript(script, callback) 時,我們必須有一個 callback 函數(shù)可供使用。換句話說,在調用 loadScript 之前,我們必須知道如何處理結果。
我們可以根據(jù)需要,在 promise 上多次調用 .then。每次調用,我們都會在“訂閱列表”中添加一個新的“粉絲”,一個新的訂閱函數(shù)。在下一章將對此內容進行詳細介紹:Promise 鏈。 只能有一個回調。

因此,promise 為我們提供了更好的代碼流和靈活性。但其實還有更多相關內容。我們將在下一章看到。

任務


用 promise 重新解決?

下列這段代碼會輸出什么?

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

  setTimeout(() => resolve(2), 1000);
});

promise.then(alert);

解決方案

輸出為:1

第二個對 resolve 的調用會被忽略,因為只有第一次對 reject/resolve 的調用才會被處理。進一步的調用都會被忽略。


基于 promise 的延時

內建函數(shù) ?setTimeout? 使用了回調函數(shù)。請創(chuàng)建一個基于 promise 的替代方案。

函數(shù) delay(ms) 應該返回一個 promise。這個 promise 應該在 ms 毫秒后被 resolve,所以我們可以向其中添加 .then,像這樣:

function delay(ms) {
  // 你的代碼
}

delay(3000).then(() => alert('runs after 3 seconds'));

解決方案

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

delay(3000).then(() => alert('runs after 3 seconds'));

請注意,在此任務中 resolve 是不帶參數(shù)調用的。我們不從 delay 中返回任何值,只是確保延遲即可。


帶有 promise 的圓形動畫

重寫任務 帶回調的圓圈動畫 的解決方案中的 ?showCircle? 函數(shù),以使其返回一個 promise,而不接受回調。

新的用法:

showCircle(150, 150, 100).then(div => {
  div.classList.add('message-ball');
  div.append("Hello, world!");
});

以任務 帶回調的圓圈動畫 的解決方案為基礎。


解決方案

使用沙箱打開解決方案。


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號