iT邦幫忙

2022 iThome 鐵人賽

DAY 19
0

明天打算介紹Math.js和MathJax如何動態渲染數學方程式,其中會用到Javascript的非同步處理Promise類別,今天就在這邊介紹它。

Javascript非同步處理程式處理是程式的流程並不是一個程序完成後,才執行下一個程序,非同步的程序會等待同步的程序完成後,再開始處理。

一直覺得非同步的命名似乎和它代表的實際意義相反。

要體會或模擬非同步處理通常用setTimeout這個指令,setTimeout的第一個參數是要執行的回呼函式,第二個參數則是設定多少微秒後執行。我們用下面的例子測試非同步與同步程序執行的流程。

console.log('測試開始');

setTimeout(() => {
  console.log('這是非同步程序');
}, 0);

console.log('同步程序結束');
// 執行結果
// 測試開始
// 同步程序結束
// 這是非同步程序

Promise

Promise主要ES6是為了解決回呼地獄(callback hell)的困境所建構的非同步類別,只要是Promise建構的實體,就是非同步執行,用下面的實例驗證。

console.log('非同步測試開始');
function toss() {
  return new Promise(() => {
    setTimeout(() => {
      const result = Math.random()
      if (result > 0.5) {
        console.log('正面')
      } else {
        console.log('反面')
      } 
    }, 100)
  })
}
toss()
console.log('同步程序結束')

執行結果

非同步測試開始
同步程序結束
undefined
反面

上面的程式只是要說明Promise的非同步處理本質,通常在建構Promise實體時,會傳入一個回呼函式,這個回呼函式最多可以有二個參數,這兩個參數負責回傳資料或錯誤訊息,第一個參數通常命名為resolve,一般用來回傳工作完成得到的結果,第二個參數命名為reject,通常回傳工作失敗的資訊。Promise的實例有兩個方法的回呼函式參數可以接收resolve和reject傳回的結果或錯誤訊息作為參數。then()的回呼函式的參數為resolve回傳的結果,catch()的回呼函式的參數為resolve回傳的結果,這兩個方法都可以鏈接Promise的實例。

console.log('非同步測試開始');
function toss() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const result = Math.random()
    if (result > 0.7) {
      resolve('正面')
    } else if (result < 0.3){
      resolve('反面')
    } else {
      reject('重丟')
    }
    }, 3000)
  })
}
toss().then(data => console.log('Success', data))
      .catch(err => console.log('Error', err))

console.dir(toss())

Promise的實例執行時,它的狀態會是pending(懸而未決)、fullfilled(完成)和reject(拒絕)三種狀態中的一種。

  • 懸而未決
    Promise
  • 完成
    fullfilled
  • 拒絕
    reject

語法糖async/await

雖然Promise的實例能使用then的方法來接收資料做後續的處理,語法上已經比回呼函式自然許多,但是追求更方便的想法是無止境的,如果能夠像下面的語法,不知有多大快人心。

const result = toss()
console.log(result)

可是如此寫法不就和同步語法一樣了嗎?ES6支援了類似同步處理的語法,讓我們程式撰寫更為自然。

async function getToss() {
  const result = await toss()
  console.log(result)
}
getToss()

也就是在處理非同步的函式前面加上關鍵字async,就可以在Promise實例前使用await關鍵字,讓語法更像同步處理,使得程式的可讀性大大提昇。

其實then也可以處理reject的回傳,但是實際上比較少人用,這邊就不提,以免愈說愈亂。

all、race

Promise類別還all和race兩個方法可以處理多個Promise實例構成陣列。all會等所有Promise實例都完成後,才算完成,回傳值是各個Promise實例的回傳值構成的陣列;而race則是第一個Promise實例完成工作,便回傳它的結果。

const f1 = () => {
  return new Promise(resolve => {
    setTimeout(() => {
    resolve('f1 success')
    }, 3000)
  })
}

const f2 = () => {
  return new Promise(resolve => {
    setTimeout(() => {
    resolve('f2 success')
    }, 5000)
  })
}

const f3 = () => {
  return new Promise(resolve => {
    setTimeout(() => {
    resolve('f3 success')
    }, 1000)
  })
}

const promise = Promise.all([f1(), f2(), f3()])
promise.then(data => {
  console.log(data)
})
console.log('同步程序結束')

上面程式,可以將「all」改成「race」便可以看出其中的差別。

程式原始碼

今日程式原始碼

今日小結

對於不熟悉Javascript的人來說,Promise是比較難理解的一個主題,而它的出現也有語言發展的歷史原因,在網頁的處理也常常需要它,所以如果對它有個粗淺的了解,對網頁的學習是相當有幫助。


上一篇
陣列常用的方法
下一篇
動態顯示MathJax
系列文
30天數學老師作互動式教學網頁30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言