「從Callback到Promise」相關筆記

總結

本篇筆記內容:

筆記

Promise

定義:

小結論:

callback 使用情境

範例:以下程式碼全部借鑑 YouTube 影片Async JS Crash Course - Callbacks, Promises, Async Await

const posts = [
  { title: 'Post 1', content: 'Content post 1' },
  { title: 'Post 2', content: 'Content post 2' }
]

function postToList () {
  setTimeout(() => {
    let content = ''
    posts.forEach(post => {
      content += `<li>${post.title}</li>`
    })
    document.body.innerHTML = content
  }, 1000)
}

postToList()
const posts = [
  { title: 'Post 1', content: 'Content post 1' },
  { title: 'Post 2', content: 'Content post 2' }
]

function postToList () {
  setTimeout(() => {
    let content = ''
    posts.forEach(post => {
      content += `<li>${post.title}</li>`
    })
    document.body.innerHTML = content
  }, 1000) // 延後1秒執行
}

function addPost (post) {
  setTimeout(() => {
    posts.push(post)
  }, 2000) // 延後2秒執行
}

addPost({ title: 'Post 3', content: 'Content post 3' })
postToList()
const posts = [
  { title: 'Post 1', content: 'Content post 1' },
  { title: 'Post 2', content: 'Content post 2' }
]

function postToList () {
  setTimeout(() => {
    let content = ''
    posts.forEach(post => {
      content += `<li>${post.title}</li>`
    })
    document.body.innerHTML = content
  }, 1000)
}

function addPost (post, callback) {
  setTimeout(() => {
    posts.push(post)
    callback()
  }, 2000)
}

addPost({ title: 'Post 3', content: 'Content post 3' }, postToList)

將 callback 轉換為 promise 形式

const posts = [
  { title: 'Post 1', content: 'Content post 1' },
  { title: 'Post 2', content: 'Content post 2' }
]

function postToList () {
  setTimeout(() => {
    let content = ''
    posts.forEach(post => {
      content += `<li>${post.title}</li>`
    })
    document.body.innerHTML = content
  }, 1000)
}

function addPost (post) {
  return new Promise((resolve, reject) => {
      setTimeout(() => {
        const error = false
        if (!error) {
          posts.push(post)
          resolve()
        } else {
          reject('something goes wrong')
        }
      })
    })
}

addPost({ title: 'Post 3', content: 'Content post 3' })
  .then(() => {
    postToList()
  })
  .catch(error => {
    console.error(error)
  })
  1. 移除addPost的 callback 參數
  2. addPost回傳 Promise 物件,其中resolve()沒有包含任何值,而reject()提供錯誤訊息「something goes wrong
  3. 執行addPost後,透過thencatch來承接addPost順利完成(resolve)或失敗(reject)的情境
  4. addPost成功時,進行postToList()
  5. addPost出現錯誤時,則console.error(error)

AC 作業應用

原始 callback 版本:

const http = require('http')
const https = require('https')
const imgPath = ''

http.createServer((req, res) => {
  https.get('https://dog.ceo/api/breeds/image/random', (body) => {
    let data = ''

    body.on('data', (chunk) => {
      data += chunk
    })

    body.on('end', () => {
      console.log(JSON.parse(data))
      imgPath = JSON.parse(data).message
      res.end(`<h1>DOG PAGE</h1><img src='${imgPath}' >`)
    })
  }).on('error', (error) => {
    console.error(error)
  })
}).listen(3000)

Promise 版起手式:

const http = require('http')
const https = require('https')

const requestData = () => {
  // TODO
}

http.createServer((req, res) => {
  // TODO
  res.end(`<h1>DOG PAGE</h1><img src='${imgPath}' >`)
}).listen(3000)

思考過程:

  1. resolve()處理JSON.parse(data),用reject()處理.on('error', (error) => { console.error(error) })這一段
  2. 把整個https.get(...)包成 Promise 物件
  3. requestData順利完成後,用then()來接JSON.parse(data)result內容為JSON.parse(data),取result.message即為圖片網址
  4. catch()來處理錯誤狀態,將workingURL替換為變數errorURL後,即可在終端看到error訊息

改裝完成:

const http = require('http')
const https = require('https')

const workingURL = 'https://dog.ceo/api/breeds/image/random'
const errorURL = 'https://this.will.not/work'

const requestData = () => {
  return new Promise((resolve, reject) => {
    https.get(workingURL, (body) => {
      let data = ''

      body.on('data', (chunk) => {
        data += chunk
      })

      body.on('end', () => {
        console.log(JSON.parse(data))
        resolve(JSON.parse(data))
      })
    }).on('error', (error) => {
      reject(error)
    })
  })
}

http.createServer((req, res) => {
  requestData()
    .then(result => {
      const imgPath = result.message
      res.end(`<h1>DOG PAGE</h1><img src='${imgPath}' >`)
    })
    .catch(error => {
      console.error(error)
      res.end(`<p>Sorry but something goes wrong.</p>`)
    })

}).listen(3000)

補充:Promise.all()

// MDN範例
const promise1 = Promise.resolve('Answer to the Ultimate Question of Life, ')
const promise2 = 'the Universe, and Everything'
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, '42')
});

Promise.all([promise1, promise2, promise3])
  .then((values) => {
    console.log(values)
})
// 輸出結果:
// ["Answer to the Ultimate Question of Life, ", "the Universe, and Everything", "42"]

參考MDN

參考文件