# 你可以用 async/await 來開發 LINE LIFF

大家好,我是做出「LINE 數位版名片」的 LINE API 專家均民。

最近 js 的世界在非同步執行的部分,經歷了三個主要世代:callbackPromise 以及 async/await,其中使用 async/await 來開發的程式碼是其中最容易維護的。

下圖取自 Node 7.6 + Koa 2: asynchronous flow control made right (opens new window)

雖然 LINE LIFF 的官方文件都是用 Promise 來寫範例,但是你知道 LINE LIFF 的 sdk 也可以使用 async/await 來進行開發嗎?

# async/await 其實只是 Promise 的語法糖 (opens new window)

當你看到

  1. 你想要用的 Function 跟你說它會回傳 Promise
  2. 範例程式碼中,使用了 .then() 的寫法

基本上就代表你可以無痛改用 async/await。以下我們將 setTimeout 包成 Promise:

function sleep(t) {
  return new Promise(function (resolve, reject) {
    setTimeout(resolve, t)
  })
}

如果你要等候 sleep() 結束,用 Promise 的寫法是這樣:

console.log('before sleep')
sleep(1000).then(function () {
  console.log('after sleep')
})

然後你也可以使用 async/await 的寫法:

async function main() {
  console.log('before sleep')
  await sleep(1000)
  console.log('after sleep')
}
main() // 最後別忘記要執行 main

# 瀏覽器相容性

以下相容性表格是取自這篇 MDN 文章 (opens new window),因為 async/await 是屬於 ES2017 的規範,基本上只要主流瀏覽器有正常更新到最新版本的話,只有 IE 不支援而已。

# 範例

接著,來示範將幾個常用的 LIFF API 改用 async/await 來寫吧!

# liff.init()

https://developers.line.biz/en/reference/liff/#initialize-liff-app (opens new window)

async function main () {
  try {
    await liff.init({
      liffId: "1234567890-abcedfgh" // 請用自己的 liffId
    })
    // 從這裡開始使用 liff 的 API
  } catch (err) {
    // 發生錯誤
    console.log(err.code, err.message)
    alert(err.message)
  }
}
main() // 最後別忘記要執行 main

# 取得環境資料

https://developers.line.biz/en/docs/liff/developing-liff-apps/#getting-environment (opens new window)

async function main () {
  await liff.init({
    liffId: "1234567890-abcedfgh" // 請用自己的 liffId
  })
  // 從這裡開始使用 liff 的 API
  const language = liff.getLanguage()
  const version = liff.getVersion()
  const isInClient = liff.isInClient()
  const isLoggedIn = liff.isLoggedIn()
  const os = liff.getOS()
}
main() // 最後別忘記要執行 main

# liff.login()

https://developers.line.biz/en/reference/liff/#login (opens new window)

通常我都會把 liff.init()liff.login() 搭配,讓我能夠在 PC 上面用瀏覽器開發:

async function main () {
  await liff.init({ liffId: '1234567890-abcedfgh' }) // 請用自己的 liffId
  // 從這裡開始使用 liff 的 API
  if (!liff.isLoggedIn()) {
    liff.login({ redirectUri: location.href })
    return
  }
  // 有登入的流程
}
main() // 最後別忘記要執行 main

# liff.getAccessToken()

https://developers.line.biz/en/docs/liff/developing-liff-apps/#getting-access-token (opens new window)

async function main () {
  try {
    await liff.init({
      liffId: "1234567890-abcedfgh" // 請用自己的 liffId
    })
    // 從這裡開始使用 liff 的 API
    if (!liff.isLoggedIn()) {
      liff.login({ redirectUri: location.href })
      return
    }
    const accessToken = liff.getAccessToken()
  } catch (err) {
    // 發生錯誤
    console.log(err.code, err.message)
    alert(err.message)
  }
}
main() // 最後別忘記要執行 main

# liff.getProfile()

https://developers.line.biz/en/reference/liff/#get-profile (opens new window)

這個 API 所回傳的 Promise 會包含 profile 資料,如果用 async/await 來寫會像是這樣:

async function main () {
  try {
    await liff.init({
      liffId: "1234567890-abcedfgh" // 請用自己的 liffId
    })
    // 從這裡開始使用 liff 的 API
    if (!liff.isLoggedIn()) {
      liff.login({ redirectUri: location.href })
      return
    }
    const profile = await liff.getProfile()
    const name = profile.displayName
  } catch (err) {
    // 發生錯誤
    console.log(err.code, err.message)
    alert(err.message)
  }
}
main() // 最後別忘記要執行 main

# liff.sendMessages()

https://developers.line.biz/en/reference/liff/#send-messages (opens new window)

傳送訊息的 API 只能在聊天視窗內打開的 LIFF 中使用,所以我會多檢查 context。

async function main () {
  try {
    await liff.init({
      liffId: "1234567890-abcedfgh" // 請用自己的 liffId
    })
    // 從這裡開始使用 liff 的 API
    if (!liff.isInClient()) throw new Error('liff.isInClient() = false')
    const contextType = (liff.getContext() || {}).type
    const notInChat = ~['utou', 'room', 'group'].indexOf(contextType)
    if (notInChat) throw new Error(`liff.getContext().type = ${contextType}`)
    await liff.sendMessages([{
      type: 'text',
      text: 'Hello world'
    }])
  } catch (err) {
    // 發生錯誤
    console.log(err.code, err.message)
    alert(err.message)
  }
}
main() // 最後別忘記要執行 main

如果希望 liff.sendMessages() 等到使用者點擊後才進行,可以改寫成這樣:

liff.init({
  liffId: "1234567890-abcedfgh" // 請用自己的 liffId
})

async function sendMessage () {
  try {
    await liff.ready // 確保 init 必須執行完畢
    // 從這裡開始使用 liff 的 API
    if (!liff.isInClient()) throw new Error('liff.isInClient() = false')
    const contextType = (liff.getContext() || {}).type
    const notInChat = ~['utou', 'room', 'group'].indexOf(contextType)
    if (notInChat) throw new Error(`liff.getContext().type = ${contextType}`)
    await liff.sendMessages([{
      type: 'text',
      text: 'Hello world'
    }])
  } catch (err) {
    // 發生錯誤
    console.log(err.code, err.message)
    alert(err.message)
  }
}
document.getElementById('sendMessageButton').addEventListener('click', sendMessage)

# Next

在我的上一篇文章「如何在 LIFF 傳送隱藏資料給機器人」 (opens new window)中,LIFF 的程式都是用 async/await 的寫法 (LIFF 原始碼) (opens new window),如果想要參考我是怎麼寫的朋友可以去看看。

另外,如果想要知道其他沒提到的 LIFF Function 要如何改寫成 async/await 可以直接告訴我,如果同時很多人想知道的話,我就會補在這篇文章裡面喔!

# 原始碼及參考連結

TIP

本文範例程式的原始碼授權為 MIT License。