【譯】怎麼寫一個JavaScript Promise

promise_banner

promise是什麼?

JavaScript promise是一個對象,表示異步任務完成或者失敗及其結果值。javascript

完結。java

我固然是開玩笑的。那麼,這個定義到底意味着什麼?git

首先,JavaScript中的許多東西都是對象。你能夠經過幾種不一樣的方式進行建立對象。最經常使用的方法是使用對象字面量語法github

const myCar = {
   color: 'blue',
   type: 'sedan',
   doors: '4',
};
複製代碼

你還能夠建立一個,並經過new關鍵字對其進行實例化。數據庫

class Car {
   constructor(color, type, doors) {
      this.color = color;
      this.type = type;
      this.doors = doors
   }
}
const myCar = new Car('blue', 'sedan', '4');
複製代碼
console.log(myCar);
複製代碼

new_class

promise只是咱們建立的對象,就像後面的例子同樣,咱們使用new關鍵字對其進行實例化。咱們傳入一個帶有兩個參數的函數,其參數爲resolvereject,而不是像傳遞給咱們Car的三個參數(顏色,類型和門)。promise

最終,promise告訴咱們一些關於咱們從它返回的異步函數的完成狀況--生效了或失敗了。咱們認爲這個功能是成功的,若是promise是解決了,而且說promise被拒絕是不成功的。服務器

const myPromise = new Promise(function(resolve, reject) {});
複製代碼
console.log(myPromise);
複製代碼

new_promise

留意,此時的promise是pending狀態網絡

const myPromise = new Promise(function(resolve, reject) {
   resolve(10);
});
複製代碼

promise_resolved

留意,咱們用10返回解決了promiseapp

看,不是太可怕 -- 只是咱們建立的對象。並且,若是咱們稍微展開一下:dom

expand_promise_resolved

留意,咱們有一些咱們能夠訪問的方法,即"then"和"catch"

此外,咱們能夠傳咱們喜歡的東西到resolvereject中。例如,咱們能夠傳遞一個對象,而不是一個字符串:

return new Promise((resolve, reject) => {
   if(somethingSuccesfulHappened) {
      const successObject = {
         msg: 'Success',
         data,//...some data we got back
      }
      resolve(successObject); 
   } else {
      const errorObject = {
         msg: 'An error occured',
         error, //...some error we got back
      }
      reject(errorObject);
   }
});
複製代碼

或者,爲了方便查看,咱們任何東西都不傳:

return new Promise((resolve, reject) => {
   if(somethingSuccesfulHappend) {
      resolve()
   } else {
      reject();
   }
});
複製代碼

定義「異步」的部分怎樣?

JavaScript是單線程的。這意味着它一次只能處理一件事。想象這麼條道路,你能夠將JavaScript視爲單車道的高速公路。特定代碼(異步代碼)能夠滑動到一邊,以容許其餘代碼越過它。完成異步代碼後,它將返回到道路。

旁註,咱們能夠從任何函數返回promise。他沒必要是異步的。話雖這麼說,promise一般在它們返回的函數是異步的狀況下返回。例如,具備將數據保存在服務器的方法API將是返回promise的絕佳候選者!

外號:

promise爲咱們提供了一種等待異步代碼完成,從中捕獲一些值,並將這些值傳遞給程序其餘部分的方法。

我這裏有篇文章深刻探討這些概念:Thrown For a Loop: Understanding Loops and Timeouts in JavaScript

咱們怎麼使用promise?

使用promise也稱爲消費promise。在上面的示例中,咱們的函數返回了一個promise對象。這容許咱們使用方法的鏈式功能。

我打賭你看到過下面的這種鏈式方法:

const a = 'Some awesome string';
const b = a.toUpperCase().replace('ST', '').toLowerCase();
console.log(b); // some awesome ring
複製代碼

如今,(僞裝)回想下咱們的promise

const somethingWasSuccesful = true;
function someAsynFunction() {
   return new Promise((resolve, reject){
      if (somethingWasSuccesful) {
         resolve();     
      } else {
         reject()
      }
   });
}
複製代碼

而後,經過鏈式方法調用咱們的promise

someAsyncFunction
   .then(runAFunctionIfItResolved(withTheResolvedValue))
   .catch(orARunAfunctionIfItRejected(withTheRejectedValue));
複製代碼

一個(更)真實的例子

想象一下,你有一個從數據庫中獲取用戶的功能。我在codepen上編寫了一個示例函數,用於模擬你可能使用的API。它提供了兩種訪問結果的選項。一,你能夠提供回調功能,在其中訪問用戶或提示錯誤。或者第二種,函數返回一個promise做爲用戶訪問或提示錯誤的方法。

爲了方便查看,我把做者的codepen上的代碼複製了下來,以下:

const users = [
  {
    id: '123',
    name: 'John Smith',
    posts: [
      {title: 'Some amazing title', content: 'Here is some amazing content'},
      {title: 'My favorite title', content: 'My favorite content'},
      {title: 'A not-so-good title', content: 'The not-so-good content'},
    ]
  },
  {
    id: '456',
    name: 'Mary Michaels',
    posts: [
      {title: 'Some amazing title', content: 'Here is some amazing content'},
      {title: 'My favorite title', content: 'My favorite content'},
      {title: 'A not-so-good title', content: 'The not-so-good content'},
    ]
  },
]
function getUserPosts(id, cb) {
 const user = users.find(el => el.id === id);
  if (cb) {
    if (user) {
      return cb(null, user);
    }
    return cb('User Not Found', null);
  }
  return new Promise(function(resolve, reject){
    if (user) {
      resolve(user);
    } else {
      reject('User not found');
    }
  });
}

/* The above code is collapsed to simulate an API you might use to get user posts for a * particular user from a database. * The API can take a callback as a second argument: getUserPosts(<id>, <callback>); * The callback function first argument is any error and second argument is the user. * For example: getUserPosts('123', function(err, user) { if (err) { console.log(err) } else { console.log(user); } }); * getUserPosts also returns a promise, for example: getUserPosts.then().catch(); * The ID's that will generate a user are the of type string and they are '123' and '456'. * All other IDs will return an error. */

getUserPosts('123', function(err, user) {
  if (err) {
    console.log(err);
  } else {
    console.log(user);
  }
});

getUserPosts('129', function(err, user) {
  if (err) {
    console.log(err);
  } else {
    console.log(user);
  }
});

getUserPosts('456')
  .then(user => console.log(user))
  .catch(err => console.log(err));
複製代碼

傳統上,咱們將經過使用回調來訪問異步代碼的結果。

rr someDatabaseThing(maybeAnID, function(err, result)) {
   //...Once we get back the thing from the database...
   if(err) {
      doSomethingWithTheError(error)
   }   else {
      doSomethingWithResults(results);
   }
}
複製代碼

在它們變得過分嵌套以前,回調的使用是能夠的。換句話說,你必須爲每一個新結果運行更多異步代碼。回調的這種模式可能會致使「回調地獄」。

callback_hell

Promise爲咱們提供了一種更優雅,更易讀的方式來查看咱們程序流程。

doSomething()
   .then(doSomethingElse) // and if you wouldn't mind
   .catch(anyErrorsPlease);
複製代碼

寫下本身的promise:金髮姑娘,三隻熊和一臺超級計算機

想象一下,你找到了一碗湯。在你喝以前,你想知道湯的溫度。可是你沒有溫度計,幸運的是,你可使用超級計算機來告訴你湯的溫度。不幸的是,這臺超級計算機最多可能須要10秒才能得到結果。

supercomputer

這裏須要有幾點須要注意:

  1. 咱們初始化了一個名爲result的全局變量。
  2. 咱們使用Math.random()setTimeout()模擬網絡延遲的持續時間。
  3. 咱們使用Manth.random()模擬溫度。
  4. 咱們經過添加一些額外的「math「將延遲和溫度限制在必定範圍內。溫度範圍是1到300;延遲範圍是1000ms到10000ms(1s到10s)。
  5. 咱們打印出延遲時間和溫度,以便咱們知道這個功能需多長時間以及咱們指望在完成時看到的結果。

運行函數並打印結果。

getTemperature(); 
console.log(results); // undefined
複製代碼

溫度是undefined,發生了什麼?

該功能須要必定的時間才能運行。在延遲結束以前,不會設置變量。所以,當咱們運行該函數時,setTimeout是異步的。setTimeout中的部分代碼移出主線程進入等待區域。

我這裏有篇文章深刻研究了這個過程:Thrown For a Loop: Understanding Loops and Timeouts in JavaScript

因爲設置變量result的函數部分移動到了等待區域直到完成,所以咱們的解析器能夠自由移動到下一行。在咱們的例子中,它是咱們的console.log()。此時,因爲咱們的setTimeout未結束,result仍未定義。

那咱們還能嘗試什麼呢?咱們能夠運行getTemperature(),而後等待11秒(由於咱們的最大延遲是10秒),而後打印出結果。

getTemperature();
   setTimeout(() => {
      console.log(result); 
   }, 11000);
// Too Hot | Delay: 3323 | Temperature: 209 deg
複製代碼

這是可行的,但這種技術問題是,儘管在咱們的例子中,咱們知道了最大的網絡延遲,但在實際中它可能偶爾須要超過10秒。並且,即便咱們能夠保證最大延遲10秒,若是result出結果了,咱們也是在浪費時間。

promise來拯救

咱們將重構getTemperature()函數以返回promise。而不是設置結果。咱們將拒絕promise,除非結果是「恰到好處」,在這種狀況下咱們將解決promise。在任何一種狀況下,咱們都會傳遞一些值到resolvereject

promise_refactor

如今,咱們可使用正在返回的promise結果(也稱爲消費promise)。

getTemperature()
   .then(result => console.log(result))
   .catch(error => console.log(error));
// Reject: Too Cold | Delay: 7880 | Temperature: 43 deg
複製代碼

.then,當咱們的promise解決時,它將被調用,並返回咱們傳遞給resolve的任何信息。

.catch,當咱們的promise拒絕時,它將被調用,並返回咱們傳遞給reject的任何信息。

最有可能的是,你將更多的使用promise,而不是建立它們。在任何狀況下,它們有助於使咱們的代碼更優雅,可讀和高效。

總結

  1. Promises是對象,其包含了有關某些異步代碼的完成以及咱們想要傳入的任何結果值的信息對象。
  2. 咱們使用return new Promise((resolve, reject)=> {})返回一個promise。
  3. 使用promise,咱們使用.then從已經解決的promise中獲取信息,而後使用.catch從拒絕的promise中獲取信息。
  4. 你可能更多地使用(消費)promises,而不是編寫它們。

參考

  1. developer.mozilla.org/en-US/docs/…

後話

相關文章
相關標籤/搜索