Node.js 8: แปลง callback เป็น Promise ด้วย util.promisify

Nuttavut Thongjor

ปัญหาหนึ่งของการใช้งาน JavaScript นั่นก็คือ Callback Hell ที่จะทำให้คุณเร่าร้อนดั่งไฟนรกทุกครั้งที่ต้องอ่านโค้ด โดยเฉพาะอย่างยิ่งกับ callback สไตล์ฉบับนู๋ Node.js นึกภาพไม่ออก? ลองดูโค้ดข้างล่างดูฮะ

JavaScript
1const fs = require('fs')
2fs.readFile('./a.js', 'utf8', (err, text) => {
3 if(err) {
4 console.log('Error', err)
5 } else {
6 // ทำอะไรซักอย่างกับข้อมูลที่ได้จากไฟล์ a แล้วค่อยอ่านไฟล์ b ต่อ
7 fs.readFile('./b.js', 'utf8', (err, text) => {
8 if(err) onsole.log('Error', err)
9 else { //... }
10 })
11 }
12})

ใน fs.readFile เรามีการส่ง callback เข้าไปเป็นอาร์กิวเมนต์ตัวสุดท้าย ในตัวอย่างนี้เมื่อไฟล์ a.js ถูกโหลดเสร็จสิ้นจึงทำการโหลด b.js ในลำดับถัดมา ด้วย callback ที่ซ้อนกันลึกแบบนี้จินตนาการซิถ้ามีซัก 3 - 5 callbacks จะเหมือนตกนรกซักแค่ไหน!

เมื่อ Callback Hell ครองเมือง ในฐานะโปรแกรมเมอร์เราจึงต้องมีที่ยืน และนั่นจึงเป็นเหตุให้ Promise และ Async / Await เกิดขึ้นเพื่อแก้ปัญหาของ callback ยังงงอยู่? เสพบทความ กำจัด Callback Hell ด้วย Promise และ Async/Await ก่อนซิ!

ช่างน่าเสียดายที่ callback ลูกทุ่งสไตล์ Nodeๆ ไม่สามารถใช้งานกับ Promise ได้ promise library ต่างๆเช่น Bluebird เลยต้องมีวิธีการแปลง callback สไตล์โหนดๆ ให้ใช้คู่กับ promise ได้ เช่น

JavaScript
1const fs = require('fs')
2const Promise = require('bluebird')
3const readFile = Promise.promisify(fs.readFile)
4
5readFile('./a.js', 'utf8')
6 .then(text => readFile('./b.js', 'utf8')
7 .then(text => //..)
8 .catch(err => console.log('Error', err))

โหยดีงามพระราม & นางสีดามากมาย แต่จะฟินกว่านี้ถ้า promisify นั้นมาพร้อมกับ Node เองเลย

รู้จักและใช้งาน promisify

พระเจ้าได้ยินเสียงคุณ จึงสถาปนาให้ Node เวอร์ชัน 8 มาพร้อมกับ promisify! เพื่อยกระดับความฟินคุณแค่เรียกใช้งานมันจาก util ดังนี้

JavaScript
1const fs = require('fs')
2// ข้าอยู่ตรงนี้
3const { promisify } = require('util')
4const readFile = promisify(fs.readFile)
5
6readFile('./a.js', 'utf8')
7 .then(text => readFile('./b.js', 'utf8')
8 .then(text => //..)
9 .catch(err => console.log('Error', err))

promisify ก็ใช้กับ Async / Await ได้นะ

เมื่อพูดถึง Promise ก็ขาดไม่ได้ที่จะยกตัวอย่างการใช้งานกับ Async / Await โค้ดของเราก็จะหน้าตาละมุนละไมหน่อย ดังนี้

JavaScript
1const fs = require('fs')
2const { promisify } = require('util')
3const readFile = promisify(fs.readFile)
4
5async function doXxx() {
6 try {
7 const aContent = await readFile('./a.js', 'utf8')
8 const bContent = await readFile('./b.js', 'utf8')
9 // ...
10 } catch (err) {
11 console.log('Error', err)
12 }
13}
14
15doXxx()

Custom promisified functions

เราสามารถกำหนดให้ util.promisify() คืนค่ากลับมาเป็นอะไรก็ได้ที่เรากำหนดขึ้นผ่าน util.promisify.custom ดังนี้

JavaScript
1const util = require('util')
2
3function foo() {
4 return 'foo'
5}
6async function bar() {
7 return 'bar'
8}
9
10foo[util.promisify.custom] = bar
11console.log(util.promisify(foo) === bar) // true

สรุป

promisify ไม่ใช่ของใหม่แต่พึ่งเกิดเป็นตัวเป็นตนใน API ของ Node เองก็ตอนเวอร์ชัน 8 นี่ละ เพื่อนๆคนไหนที่คันไม้คันมือทุกครั้งที่ต้องทนใช้ callback สไตล์โหนดๆ จะลองหยิบจับ promisify มาใช้คู่กับ Promise บ้างก็กิ๊บเก๋ดีนะ

เอกสารอ้างอิง

Node.js v8.1.0 Documentation. Retrieved June, 13, 2017, from https://nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original

Dr. Axel Rauschmayer (2017). Node.js 8: util.promisify(). Retrieved June, 13, 2017, from http://2ality.com/2017/05/util-promisify.html

สารบัญ

สารบัญ

  • รู้จักและใช้งาน promisify
  • promisify ก็ใช้กับ Async / Await ได้นะ
  • Custom promisified functions
  • สรุป
  • เอกสารอ้างอิง