国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

ES6 Promise:模式與反模式

djfml / 3384人閱讀

摘要:盡管可以讓代碼更加簡潔易讀,但對于只熟悉回調函數的人來說,可能對此還是會有所懷疑。始終避免在或使用回調函數,否則會吞噬任何后續的錯誤,將其作為鏈的一部分。然而,使用回調函數,使用所謂的,即第一個參數是一個錯誤回調變得很常見。

原文:ES6 Promises: Patterns and Anti-Patterns
作者:Bobby Brennan

當幾年前,第一次使用 NodeJS 的時候,對現在被稱為“ 回調地獄 ”的寫法感到很困擾。幸運的是,現在是 2017 年了,NodeJS 已經采用大量 JavaScript 的最新特性,從 v4 開始已經支持 Promise。

盡管 Promise 可以讓代碼更加簡潔易讀,但對于只熟悉回調函數的人來說,可能對此還是會有所懷疑。在這里,將列出我在使用Promise 時學到的一些基本模式,以及踩的一些坑。

注意:在本文中將使用箭頭函數 ,如果你還不是很熟悉,其實很簡單,建議先讀一下使用它們的好處

模式與最佳實踐 使用 Promise

如果使用的是已經支持 Promise 的第三方庫,那么使用起來非常簡單。只需關心兩個函數:then()catch()。例如,有一個客戶端 API 包含三個方法,getItem()updateItem(),和deleteItem(),每一個方法都返回一個 Promise:

Promise.resolve()
  .then(_ => {
    return api.getItem(1)
  })
  .then(item => {
    item.amount++
    return api.updateItem(1, item);
  })
  .then(update => {
    return api.deleteItem(1);
  })
  .catch(e => {
    console.log("error while working on item 1");
  })

每次調用 then() 會在 Promise 鏈中創建一個新的步驟,如果鏈中的任何一個地方出現錯誤,就會觸發接下來的 catch()then()catch() 都可以返回一個值或者一個新的 Promise,結果將被傳遞到 Promise 鏈的下一個then()

為了比較,這里使用回調函數來實現相同邏輯:

api.getItem(1, (err, data) => {
  if (err) throw err;
  item.amount++;
  api.updateItem(1, item, (err, update) => {
    if (err) throw err;
    api.deleteItem(1, (err) => {
      if (err) throw err;
    })
  })
})

要注意的第一個區別是,使用回調函數,我們必須在過程的每個步驟中進行錯誤處理,而不是用單個的 catch-all 來處理。回調函數的第二個問題更直觀,每個步驟都要水平縮進,而使用 Promise 的代碼則有顯而易見的順序關系。

回調函數 Promise 化

需要學習的第一個技巧是如何將回調函數轉換為 Promise。你可能正在使用仍然基于回調的庫,或是自己的舊代碼,不過不用擔心,因為只需要幾行代碼就可以將其包裝成一個 Promise。這是將 Node 中的一個回調方法 fs.readFile 轉換為 Promise的示例:

function readFilePromise(filename) {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, "utf8", (err, data) => {
      if (err) reject(err);
      else resolve(data);
    })
  })
}

readFilePromise("index.html")
  .then(data => console.log(data))
  .catch(e => console.log(e))

關鍵部分是 Promise 構造函數,它接收一個函數作為參數,這個函數有兩個函數參數:resolvereject。在這個函數里完成所有工作,完成之后,在成功時調用 resolve,如果有錯誤則調用 reject

需要注意的是只有一個resolve 或者 reject 被調用,即應該只被調用一次。在我們的示例中,如果 fs.readFile 返回錯誤,我們將錯誤傳遞給 reject,否則將文件數據傳遞給resolve

Promise 的值

ES6 有兩個很方便的輔助函數,用于通過普通值創建 Promise:Promise.resolve()Promise.reject()。例如,可能需要在同步處理某些情況時一個返回 Promise 的函數:

function readFilePromise(filename) {
  if (!filename) {
    return Promise.reject(new Error("Filename not specified"));
  }
  if (filename === "index.html") {
    return Promise.resolve("

Hello!

"); } return new Promise((resolve, reject) => {/*...*/}) }

注意,雖然可以傳遞任何東西(或者不傳遞任何值)給 Promise.reject(),但是好的做法是傳遞一個Error

并行運行

Promise.all是一個并行運行 Promise 數組的方法,也就是說是同時運行。例如,我們有一個要從磁盤讀取文件的列表。使用上面創建的 readFilePromise 函數,將如下所示:

let filenames = ["index.html", "blog.html", "terms.html"];

Promise.all(filenames.map(readFilePromise))
  .then(files => {
    console.log("index:", files[0]);
    console.log("blog:", files[1]);
    console.log("terms:", files[2]);
  })

我甚至不會使用傳統的回調函數來嘗試編寫與之等效的代碼,那樣會很凌亂,而且也容易出錯。

串行運行

有時同時運行一堆 Promise 可能會出現問題。比如,如果嘗試使用 Promise.all 的 API ??去檢索一堆資源,則可能會在達到速率限制時開始響應429錯誤。

一種解決方案是串行運行 Promise,或一個接一個地運行。但是在 ES6 中沒有提供類似 Promise.all 這樣的方法(為什么?),但我們可以使用 Array.reduce 來實現:

let itemIDs = [1, 2, 3, 4, 5];

itemIDs.reduce((promise, itemID) => {
  return promise.then(_ => api.deleteItem(itemID));
}, Promise.resolve());

在這種情況下,我們需要等待每次調用 api.deleteItem() 完成之后才能進行下一次調用。這種方法,比為每個 itemID 寫 .then() 更簡潔更通用:

Promise.resolve()
  .then(_ => api.deleteItem(1))
  .then(_ => api.deleteItem(2))
  .then(_ => api.deleteItem(3))
  .then(_ => api.deleteItem(4))
  .then(_ => api.deleteItem(5));
Race

ES6 提供的另一個很方便的函數是 Promise.race。跟 Promise.all 一樣,接收一個 Promise 數組,并同時運行它們,但不同的是,會在一旦任何 Promise 完成或失敗的情況下返回,并放棄所有其他的結果。

例如,我們可以創建一個在幾秒鐘之后超時的 Promise:

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

Promise.race([readFilePromise("index.html"), timeout(1000)])
  .then(data => console.log(data))
  .catch(e => console.log("Timed out after 1 second"))

需要注意的是,其他 Promise 仍將繼續運行 ,只是看不到結果而已。

捕獲錯誤

捕獲錯誤最常見的方式是添加一個 .catch() 代碼塊,這將捕獲前面所有 .then() 代碼塊中的錯誤 :

Promise.resolve()
  .then(_ => api.getItem(1))
  .then(item => {
    item.amount++;
    return api.updateItem(1, item);
  })
  .catch(e => {
    console.log("failed to get or update item");
  })

在這里,只要有 getItem 或者 updateItem 失敗,catch()就會被觸發。但是如果我們想分開處理 getItem 的錯誤怎么辦?只需再插入一個catch() 就可以,它也可以返回另一個 Promise。

Promise.resolve()
  .then(_ => api.getItem(1))
  .catch(e => api.createItem(1, {amount: 0}))
  .then(item => {
    item.amount++;
    return api.updateItem(1, item);
  })
  .catch(e => {
    console.log("failed to update item");
  })

現在,如果getItem()失敗,我們通過第一個 catch 介入并創建一條新的記錄。

拋出錯誤

應該將 then() 語句中的所有代碼視為 try 塊內的所有代碼。return Promise.reject()throw new Error() 都會導致下一個 catch() 代碼塊的運行。

這意味著運行時錯誤也會觸發 catch(),所以不要去假設錯誤的來源。例如,在下面的代碼中,我們可能希望該 catch() 只能獲得 getItem 拋出的錯誤,但是如示例所示,它還會在我們的 then() 語句中捕獲運行時錯誤。

api.getItem(1)
  .then(item => {
    delete item.owner;
    console.log(item.owner.name);
  })
  .catch(e => {
    console.log(e); // Cannot read property "name" of undefined
  })
動態鏈

有時,我們想要動態地構建 Promise 鏈,例如,在滿足特定條件時,插入一個額外的步驟。在下面的示例中,在讀取給定文件之前,我們可以選擇創建一個鎖定文件:

function readFileAndMaybeLock(filename, createLockFile) {
  let promise = Promise.resolve();

  if (createLockFile) {
    promise = promise.then(_ => writeFilePromise(filename + ".lock", ""))
  }

  return promise.then(_ => readFilePromise(filename));
}

一定要通過重寫 promise = promise.then(/*...*/) 來更新 Promise 的值。參看接下來反模式中會提到的 多次調用 then()

反模式

Promise 是一個整潔的抽象,但很容易陷入某些陷阱。以下是我遇到的一些最常見的問題。

重回回調地獄

當我第一次從回調函數轉到 Promise 時,發現很難擺脫一些舊習慣,仍像使用回調函數一樣嵌套 Promise:

api.getItem(1)
  .then(item => {
    item.amount++;
    api.updateItem(1, item)
      .then(update => {
        api.deleteItem(1)
          .then(deletion => {
            console.log("done!");
          })
      })
  })

這種嵌套是完全沒有必要的。有時一兩層嵌套可以幫助組合相關任務,但是最好總是使用 .then() 重寫成 Promise 垂直鏈 。

沒有返回

我遇到的一個經常會犯的錯誤是在一個 Promise 鏈中忘記 return 語句。你能發現下面的 bug 嗎?

api.getItem(1)
  .then(item => {
    item.amount++;
    api.updateItem(1, item);
  })
  .then(update => {
    return api.deleteItem(1);
  })
  .then(deletion => {
    console.log("done!");
  })

因為我們沒有在第4行的 api.updateItem() 前面寫 return,所以 then() 代碼塊會立即 resolove,導致 api.deleteItem() 可能在 api.updateItem() 完成之前就被調用。

在我看來,這是 ES6 Promise 的一個大問題,往往會引發意想不到的行為。問題是, .then() 可以返回一個值,也可以返回一個新的 Promise,undefined 完全是一個有效的返回值。就個人而言,如果我負責 Promise API,我會在 .then() 返回 undefined 時拋出運行時錯誤,但現在我們需要特別注意 return 創建的 Promise。

多次調用 .then()

根據規范,在同一個 Promise 上多次調用 then() 是完全有效的,并且回調將按照其注冊順序被調用。但是,我并未見過需要這樣做的場景,并且在使用返回值和錯誤處理時可能會產生一些意外行為:

let p = Promise.resolve("a");
p.then(_ => "b");
p.then(result => {
  console.log(result) // "a"
})

let q = Promise.resolve("a");
q = q.then(_ => "b");
q = q.then(result => {
  console.log(result) // "b"
})

在這個例子中,因為我們在每次調用 then() 不更新 p 的值,所以我們看不到 "b" 返回。但是每次調用 then() 時更新 q,所以其行為更可預測。

這也適用于錯誤處理:

let p = Promise.resolve();
p.then(_ => {throw new Error("whoops!")})
p.then(_ => {
  console.log("hello!"); // "hello!"
})

let q = Promise.resolve();
q = q.then(_ => {throw new Error("whoops!")})
q = q.then(_ => {
  console.log("hello"); // We never reach here
})

在這里,我們期望的是拋出一個錯誤來打破 Promise 鏈,但由于沒有更新 p 的值,所以第二個 then() 仍會被調用。

有可能在一個 Promise 上多次調用 .then() 有很多理由 ,因為它允許將 Promise 分配到幾個新的獨立的 Promise 中,但是還沒發現真實的使用場景。

混合使用回調和 Promise

很容易進入一種陷阱,在使用基于 Promise 庫的同時,仍在基于回調的項目中工作。始終避免在 then()catch() 使用回調函數?,否則 Promise 會吞噬任何后續的錯誤,將其作為 Promise 鏈的一部分。例如,以下內容看起來是一個挺合理的方式,使用回調函數來包裝一個 Promise:

function getThing(callback) {
  api.getItem(1)
    .then(item => callback(null, item))
    .catch(e => callback(e));
}

getThing(function(err, thing) {
  if (err) throw err;
  console.log(thing);
})

這里的問題是,如果有錯誤,我們會收到關于“Unhandled promise rejection”的警告,即使我們添加了一個 catch() 代碼塊。這是因為,callback()then()catch() 都會被調用,使之成為 Promise 鏈的一部分。

如果必須使用回調來包裝 Promise,可以使用 setTimeout (或者是 NodeJS 中的 process.nextTick)來打破 Promise:

function getThing(callback) {
  api.getItem(1)
    .then(item => setTimeout(_ => callback(null, item)))
    .catch(e => setTimeout(_ => callback(e)));
}

getThing(function(err, thing) {
  if (err) throw err;
  console.log(thing);
})
不捕獲錯誤

JavaScript 中的錯誤處理有點奇怪。雖然支持熟悉的 try/catch 范例,但是沒有辦法強制調用者以 Java 的方式處理錯誤。然而,使用回調函數,使用所謂的“errbacks”,即第一個參數是一個錯誤回調變得很常見。這迫使調用者至少承認錯誤的可能性。例如,fs 庫:

fs.readFile("index.html", "utf8", (err, data) => {
  if (err) throw err;
  console.log(data);
})

使用 Promise,又將很容易忘記需要進行錯誤處理,特別是對于敏感操作(如文件系統和數據庫訪問)。目前,如果沒有捕獲到 reject 的 Promise,將在 NodeJS 中看到非常丑的警告:

(node:29916) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: whoops!
(node:29916) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

確保在主要的事件循環中任何 Promise 鏈的末尾添加 catch() 以避免這種情況。

總結

希望這是一篇有用的關于常見 Promise 模式和反模式的概述。如果你想了解更多,這里有一些有用的資源:

Mozilla 的 ES6 Promise 文檔

來自 Google 的 Promise 介紹

Dave Atchley 的 ES6 Promise 概述

更多的 Promise 模式和反模式

或者閱讀來自 DataFire 團隊的內容

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/88776.html

相關文章

  • 2017-09-30 前端日報

    摘要:前端日報精選劉海打理指北中的錯誤處理模式與反模式譯圖解和譯你并不知道中文裝飾器讓你的代碼更簡潔眾成翻譯第期每個程序員第一份工作前應該知道的件事中的不變性眾成翻譯寫的一次小結掘金內部機制探秘和文末附彩蛋和源碼前端雜談開發實戰 2017-09-30 前端日報 精選 iPhone X 劉海打理指北React16中的錯誤處理ES6 Promise:模式與反模式「譯」圖解 ArrayBuffer...

    darryrzhong 評論0 收藏0
  • 談談ES6前后的異步編程

    摘要:回調函數這是異步編程最基本的方法。對象對象是工作組提出的一種規范,目的是為異步編程提供統一接口。誕生后,出現了函數,它將異步編程帶入了一個全新的階段。 更多詳情點擊http://blog.zhangbing.club/Ja... Javascript 語言的執行環境是單線程的,如果沒有異步編程,根本沒法用,非卡死不可。 為了解決這個問題,Javascript語言將任務的執行模式分成兩種...

    fizz 評論0 收藏0
  • ES6-7

    摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...

    mudiyouyou 評論0 收藏0
  • 「前端早讀君001」基于ES6Promise封裝的圖片資源加載通用函數(適用于vue)

    摘要:又譬如在一個多圖展示的網頁,由于圖片過多或圖片太大,我們希望圖片加載完再一次性顯示,而不是東一張西一張陸續顯示,這時候也需要用圖片一次性加載功能。 基于promise的圖片資源一次性加載或者預加載 作者:NEXT卓 場景描述 不是每個網頁端的用戶都能用得起光纖,不是每張圖片都是壓縮得很小,有時候我們也想要看高清大圖,但是受限于網速有時候場景是這樣的:(很明顯左邊的第一張圖片還沒出來,其...

    cyrils 評論0 收藏0
  • 「前端早讀君001」基于ES6Promise封裝的圖片資源加載通用函數(適用于vue)

    摘要:又譬如在一個多圖展示的網頁,由于圖片過多或圖片太大,我們希望圖片加載完再一次性顯示,而不是東一張西一張陸續顯示,這時候也需要用圖片一次性加載功能。 基于promise的圖片資源一次性加載或者預加載 作者:NEXT卓 場景描述 不是每個網頁端的用戶都能用得起光纖,不是每張圖片都是壓縮得很小,有時候我們也想要看高清大圖,但是受限于網速有時候場景是這樣的:(很明顯左邊的第一張圖片還沒出來,其...

    Eric 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<