iT邦幫忙

0

JavaScript. promise 非同步觀念

promise 經常與 Ajax 共同談論,但這篇文章會以 promise 為主;promise 是一個語法,專門用來處理與優化非同步行為,我們知道 JavaScript 屬於同步程式語言,因此它一次只會做一件事,遇到非同步事件的時候,就會等到所有的原始碼運行完,才會執行非同步的事件。

這邊強調的是執行的順序,只要是非同步一定都會放在最後才執行,即使 setTimeout() 的時間設定是 0,也同樣放到最後。

function getData() {
	setTimeout(() => {
    console.log('... 已取得遠端資料');
  }, 0);
}
const component = {
  init() {
    console.log(1); 
    getData(); // 非同步
    console.log(2); 
  }
}
component.init(); 

結果:

1
2
... 已取得遠端資料

Promise 結構

promise 是一個建構式函式,函式也屬於物件,我們可以附加其他屬性方法,promise 可以直接使用 allraceresolvereject 等方法,下面這幾種方法皆可以直接 console 查看:

  • promise.all
  • promise.race
  • promise.resolve
  • promise.reject

promise 建構函式 new 出的新物件,也可以在 prototype 內使用其中的原型方法,在這裡面也包含了 thencatchfinally,如果要呼叫他們,則必須在新產生的物件下才能呼叫。

透過 new Promise() 的方法建立 a 物件,a 就能使用 promise 的原型方法,範例如下:

const a = new Promise();

a.then(); // promise 回傳正確
a.catch(); // promise 回傳失敗
a.finally(); // 非同步執行完畢(無論是否正確完成,非同步都會在最後被執行完畢)

在 promise 事件裡,我們必須給予參數讓它回傳結果,通常會給予 resolvereject 這兩個參數,他們分別代表回傳成功及失敗,並且僅能回傳其中一個。另外,這兩個參數也可以自己定義名稱,但大部分的開發者會習慣用本來的名字。

new Promise(function(resolve, reject){
	resolve(); // 正確完成的回傳
	reject(); // 失敗的回傳
});

Promise 狀態

promise 的執行過程中也包括下面這幾種狀態:

  • pending / 事件正在運行中,但還沒有獲得結果。
  • resolved / 事件已經正確執行完畢,並回傳 resolve 的結果,resolve 會回傳成功。
  • rejected / 事件已經執行完畢,並回傳 reject 的結果,reject 會回傳錯誤。

以下範例可以大致說明 promise 的運行過程:

const promiseSetTimeout = (status) => {
  return new Promise((resolve, reject) => {
		// setTimeout 在這裡就是一個非同步的狀態
    setTimeout(() => {
      if (status) {
        resolve('promiseSetTimeout 成功')
      } else {
        reject('promiseSetTimeout 失敗')
      }
    }, 0);
  })
}

pending 之後會有兩個狀態 Fulfilled 和 Rejected,Fulfilled ( 成功 ) 會使用 resolve 回傳結果,並用 then 來做接收,Rejected ( 失敗 ) 會使用 reject 回傳結果,並用 thencatch 來做接收,但如果前面有 then,則會跳過,直接用 catch 做接收。

另外,我們要如何確定 promise 是否完成?這時候可以依據 resolvereject 是否有被調用,如果沒有被調用, promise 的結果會停留在 pending

function promise() {
  return new Promise((resolve, reject) => {});
}

console.dir(promise()); // [[PromiseState]]: "pending"

執行上面程式碼,我們可以看到在 promise 函式中有下面兩種屬性:

  • [[PromiseStatus]]:"pending" 目前的進度狀態。
  • [[PromiseValue]]:undefinedresolvereject 回傳的值。
promise
	[[Prototype]]: Promise
	[[PromiseState]]: "pending"
	[[PromiseResult]]: undefined

建立 Promise

函式陳述式建立以後,透過 return new Promise 回傳並建立一個 promise 物件,在內部執行 promise 函式並加入參數 resolvereject,這個階段就是常見的 promise 結構,接著等待 resolvereject 回傳結果就可以完成整個程序。

這個範例會隨機調用 resolve 和 reject:

function promise() {
  return new Promise((resolve, reject) => {
    // 隨機取得 0 or 1
    const num = Math.random() > 0.5 ? 1 : 0;

    // 1 則執行 resolve,否則執行 reject
    if (num) { 
      resolve('成功');
    }
    reject('失敗')
  });
}

第二個範例也可以看到函式最後的結果是回傳 resolve,並且能夠回傳 resolved 的狀態和值。

const promiseSetTimeout = (status) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (status) {
        resolve('promiseSetTimeout 成功')
      } else {
        reject('promiseSetTimeout 失敗')
      }
    }, 0);
  })
}
promiseSetTimeout(true)
	.then(function(res){
	console.log(res); // "promiseSetTimeout 成功" 回傳成功
})

結果:

Promise {<pending>}
	[[Prototype]]: Promise
	[[PromiseState]]: "fulfilled"
	[[PromiseResult]]: undefined

Promise 鏈接

promise 在 thencatch 都可以使用鏈接的方式不斷的進行任務,這個範例的 promise 傳入 0 會調用 reject,其他數值則是 resolve

function promise(num) {
  return new Promise((resolve, reject) => {
    num ? resolve(`${num}, 成功`) : reject('失敗');
  });
}

如果我們希望 promise 可以接著進行下一個任務,可使用 return 進入下一個 then,這邊的 return 也有一些特點:

  • 不侷限於 promise 函式,任何表達式都可進行回傳。
  • promise 函式的 return 會遵循 thencatch 的運作。
  • 如果不是 promise 函式,再下一個 then 則可以取得結果。
promise(1)
  .then(success => {
    console.log(success);
    return promise(2);
  })
  .then(success => {
    console.log(success);
    return promise(0); // 這個階段會進入 catch
  })
  .then(success => {   // 由於上一個階段結果是 reject,所以此段不執行
    console.log(success);
    return promise(3);
  })
  .catch(fail => {
    console.log(fail);
  })

Promise 方法

上面有提到幾個 promise 的方法,這邊回頭來討論一下,在 promise物件下,展開可以看到這些方法。

  • promise.all

以陣列的形式傳入數個 promise 函式,並且以同樣的順序回傳,且為陣列結果。

Promise.all([promise(1), promise(2), promise(3, 3000)])
  .then(res => {
    console.log(res);
  });
  • promise.race

以陣列的形式傳入數個 promise 函式,但在全部執行完畢以後只會回傳單一個結果,並且回傳的是第一個執行完畢的陣列,以下列範例來說,回傳的是 promise(1)

Promise.race([promise(1), promise(2), promise(3, 3000)]).then(res => {
  console.log(res);
});
  • promise.resolve 和 promise.reject

這兩個方法其實就是回傳 promise 運行完畢的狀態;resolve 回傳操作成功,reject 回傳操作失敗,並且通常只會回傳其中一個結果。

var result = Promise.resolve('result');
result.then(res => {
  console.log('resolved', res); // 成功部分可以正確接收結果
}, res => {
  console.log('rejected', res); // 失敗部分不會取得結果
});

此篇文章感謝卡斯伯老師詳細的文章講解,為了學習和記憶我也盡量用自己的話整理出來,如果有理解錯誤的地方也請看過的大神們不吝嗇告知,或也可以看看卡斯伯老師詳細的 promise 全介紹。

參考資料:

JavaScript Promise 全介紹


尚未有邦友留言

立即登入留言