Let’s get familiar with “Mapping & Flattening” operator in RxJS #2 (END)

Vorrawut Judasri
odds.team
Published in
7 min readSep 17, 2019

จากบทความก่อนหน้าที่เราได้ทำความเข้าใจกับคำว่า “Mapping” และ “Flattening” และได้เรียนรู้รูปแบบการใช้งานของ Flattening ทั้ง 4 แบบ ที่เราจำเป็นต้องพิจารณาก่อนที่จะนำมาใช้งานว่าอยากได้ผลลัพธ์ออกมาเป็นอย่างไร โดยถ้าใครยังไม่ได้อ่าน ผมก็อยากเชิญชวนให้ลองทำความเข้าใจดูก่อนนะครับ เพราะเนื้อหาในบทความนี้เป็นส่วนหลักที่ต้องมีความเข้าใจจากเรื่องที่ได้กล่าวไปก่อนหน้า โดยสามารถเข้าไปอ่านก่อนได้ที่

ก่อนอื่นเลยก็ขอเปิดตัวเหล่าฮีโร่ทั้ง 4 ตัวและเป็นตัวละครหลักของเรื่องนี้ ซึ่งเกิดมาจากการรวมความสามารถกันของทั้ง Mapping และ Flattening เข้าด้วยกัน . . .

4 ตัวละครหลักใน “Mapping & Flattening” Operator

ทั้ง 4 ตัวละครที่จะพูดถึงนี้มีรูปแบบการทำงานหรือพฤติกรรมเหมือนๆ กัน แต่ความสามารถของแต่ละตัวจะแตกต่างขึ้นอยู่กับการนำไปใช้งาน โดยขอเริ่มที่ตัวแรกก่อนเลยครับ ซึ่งก็คือ switchMap นั่นเอง

switchMap

switchMap (map + switchAll) : “Fashion” Operator

Emits items only from the most recent inner Observable.

switchMap นั้นเกิดจาก operator “map” ที่รวมกับความสามารถ “switchAll “(รูปแบบ switch ที่เป็นเวอร์ชั่นที่สามารถใช้ chain ใน pipe ได้)

โดยสามารถนิยามมันว่าเป็น Operator “Fashion” ได้เลย เพราะมันจะสนใจแค่ค่าที่เข้ามาใหม่สุดเสมอ ตัวเก่าก่อนหน้าที่ทำค้างไว้จะละทิ้งและ unsubscribe() ทันที ดังนั้นค่าที่มันจะคืนกลับมาให้เราจะเป็นตัวที่มาใหม่เท่านั้น! ของที่มาใหม่และเป็นที่ต้องการหรือนิยมมักจะก่อให้เกิดแฟชั่นขึ้นเสมอๆ นะครับ ของที่หมดความสนใจไปแล้วก็จะถูกละทิ้งไป

โดยเพื่อให้เข้าใจง่ายยิ่งขึ้น จะขอหยิบยกตัวอย่างสถานการณ์เพื่อจำลองการทำงานของ switchMap กันนะครับ

อันดับแรกอยากให้นึกว่า ตัวคุณเองได้เริ่มทำงานเป็น Developer และเข้าทำงานที่บริษัท IT แห่งหนึ่ง ซึ่งคุณก็ทำงานร่วมกับเพื่อนๆ ในทีมของคุณ

สถานการณ์จำลอง

ต่อมาทีมของคุณเองก็ได้รับมอบหมายงานจากบอสของคุณเอง โดยบอสของคุณได้บอกทีมของคุณและตัวคุณเองว่า “เค้าอยากให้ทีมของเราไปทำ Feature A มาให้หน่อย”

Feature A arrived!

ทุกอย่างก็ดูปกติดีเป็นไปตามรูปแบบการทำงานทั่วไป เริ่มต้นจากทีมของคุณได้งานเข้ามาในทีม หลังจากนั้นก็เริ่มมีการไปรวบรวมข้อมูลที่จะเอากลับมาวางแผนและต่อด้วยการพัฒนาโปรแกรมขึ้นมา เป็นลักษณะนี้ไปเรื่อยๆ

ภาพของการทำงานกับ feature A ที่เข้ามา

แต่แล้วในระหว่างที่ทีมของคุณกำลังทำงานกับ Feature A ตามปกติ เซอร์ไพรส์ก็เกิดขึ้นมาเมื่อบอสของคุณได้ไอเดียใหม่มาและได้มีการเรียกทีมของคุณเข้าไปเพื่อบอกข่าวดีว่า “ทีมของคุณนั้นจะต้องเริ่มทำ Feature B ทันที”

และ Feature B ก็มาหลังจากที่เราทำ Feature A ไปสักพัก

เรื่องที่น่าเศร้าก็คือ “งานใหม่ที่เข้ามานั้นร้อนและเร่งด่วนกว่าตัวเก่าก่อนหน้าซะอีก” 😥

ผลของการที่งานใหม่มันสำคัญและเร่งด่วนกว่า เลยทำให้ภาพของการทำงานภายในทีมนั้นเปลี่ยนไป จากเดิมที่ทุกคนทำ Feature A ด้วยกันอยู่ ก็ต้องเปลี่ยนมาทำ Feature B แทนหมด เพื่อให้เสร็จทันกำหนดที่บอสอยากได้

สถานการณ์จำลองของทีมกับ feature B ที่เข้ามาแทรก โดยให้หยุดทำ feature A ไปเลย แล้วเปลี่ยนมาทำ B แทน

โดยในภาพด้านบนเราจะเห็นได้ว่าในตอนแรกทีมของเรา เมื่อมี feature A เข้ามาเราก็จะเริ่มรวบรวมข้อมูลที่หามาได้นั้นมาวางแผนและลุยงานกันเลย โดยข้อมูลที่หามาได้ก็มีมาเพิ่มเรื่อยๆ และก็เป็นลักษณะนี้ไปเรื่อยๆ

ต่อมาเมื่อมี feature B เข้ามาแทรกในระหว่างที่ทีมกำลังทำงานเก่าอยู่ซึ่งก็คือ feature A นั่นเอง เหตุการณ์ที่เกิดขึ้นก็คือ ทีมก็ต้องเปลี่ยนไปรวบรวมข้อมูลของงาน B เพื่อมาทำ โดยระหว่างนั้นก็ไม่สนใจงาน A ที่ทำไปก่อนหน้าเลย เพราะบอสบอกกับทีมเราว่า “ให้ทำงาน B แทน” นั้นเอง จะเห็นได้ว่าท้ายที่สุดทีมของคุณก็จะโฟกัสแค่งาน B เท่านั้น!

ภาพของทีมกับ feature B ที่เข้ามาแทรก โดยให้หยุดทำ feature A ไปเลย แล้วเปลี่ยนมาทำ B แทน (ภาพผลลัพธ์ท้ายสุด)

Coding#1

คราวนี้เรามาลองเปรียบเทียบเหตุการณ์ที่เกิดขึ้นก่อนหน้าด้วยรูปแบบของ RxJS กันบ้างนะครับว่าจะเกิดอะไรขึ้นบ้างในแต่ละขั้นตอนด้วยการอธิบายแบบ Coding กัน :]

โดยเริ่มต้นเราจะมี featureObservable ซึ่งเป็นตัวแปรที่ด้านในมี feature A และ B อยู่ (โดยสมมุติให้ feature A และ B เข้ามาตามคนละช่วงของเวลา ซึ่ง feature A จะเข้ามาก่อนแล้วตามด้วย B)

หลังจากนั้นเราจะทำการ map feature ที่เข้ามากับการไปรวบรวมข้อมูลมาทำงาน โดยผลลัพธ์ที่ได้จะกลายมาเป็นข้อมูลที่ทีมของเราจะเอาไปทำงานกัน และต่อจากนั้นก็จะมีการนำข้อมูลดังกล่าวไปวางแผนและเริ่มต้นทำงานกันในทีม

ภาพจำลองการทำงานของ switchMap กับ feature ที่เข้ามาในทีม

จากภาพจำลองด้านบนจะเห็นได้ว่า ในตอนเริ่มต้นทีมของเราจะมี feature A ที่ได้รับจากบอสเข้ามา หลังจากนั้นก็จะได้เป็นข้อมูลที่เราจะต้องเอาไปทำงานต่อและต่อไปก็เริ่มวางแผนและแตกเป็นใบย่อยๆ (Task) หลายๆใบ ซึ่งพอถึงตรงนี้ทีมก็สามารถที่จะหยิบเอาไปทำงานได้เองตามรายละเอียดนั้นๆ

ทุกอย่างดูปกติดี ทีมก็ทำงาน Feature A ไปเรื่อยๆและเริ่มมีงานย่อยๆ ของ A ที่เสร็จเรียบร้อยบ้างแล้ว ต่อมาเมื่อ Feature B เข้ามาแทรกในขณะที่ A ยังทำงานไม่เสร็จเรียบร้อยเพราะยังมี Task ของงาน A ที่ยังไม่ได้ทำ จะเห็นได้ว่าหลังจากที่งาน B เข้ามาแล้วงาน A จะหายไปทันทีและทีมก็จะเปลี่ยนไปทำงาน B ทันทีและจะไม่มีการกลับไปทำงาน A อีกแล้ว โดยในท้ายที่สุดทีมของเราก็จะได้ทำงาน B จนไปเรื่อยๆ ตราบใดที่ไม่มี feature ใหม่เข้ามา

Code ตัวอย่างและผลลัพธ์ตอนท้ายสุดในการทำงานของ switchMap กับ feature ที่เข้ามาในทีม

มาลองดู Marble Diagram ของ switchMap กันบ้าง

โดยกำหนดให้เส้นลูกศรสีขาวที่อยู่บนสุดนั้นเป็นเส้นของ Value ที่รับเข้ามาตามช่วงของเวลาโดยจะมี Value เข้ามา 3 ค่า คือ ลูกบอลสีฟ้า ลูกบอลสีเขียวและลูกบอลสีแดงเข้ามาตามลำดับ และให้ลูกศรสีขาวที่อยู่ตำแหน่งด้านล่างสุดคือ เส้นของผลลัพธ์ที่เราจะได้จากกระบวนการทำงานที่เกิดขึ้นตามช่วงของเวลาเช่นกัน

Marble Diagram อธิบายการทำงานของ switchMap

หลังจากที่ลูกบอลแต่ละลูก (Value) ที่รับเข้ามานั้นจะต้องผ่านการทำงานกับกล่องสีเขียวที่อยู่ตรงกลางก่อนซึ่งเป็นด้านในมี switchMap และ logic ในการ transform ของที่เข้ามาโดยเปลี่ยนจากลูกบอลให้กลายเป็นสีเหลี่ยมขนมเปียกปูน 2 ลูกถึงจะจบการทำงานได้

โดยจะพบว่าเมื่อลูกบอลทั้งสามสีที่เข้ามาตามช่วงเวลานั้นเมื่อผ่าน logic ก็จะได้ ผลลัพธ์ออกไปในอยู่ลูกศรสีขาวด้านล่างตามเส้นลูกศรสีฟ้า และได้เกิดสิ่งที่น่าสนใจตรงที่เมื่อลูกบอลสีแดงได้เข้ามาในเวลาขณะที่ลูกบอลสีเขียวยังทำงานไม่เสร็จ (ยังไม่ได้สี่เหลี่ยมขนมเปียกปูนครบ 2 ลูก) จะพบว่าลูกบอลสีเขียวที่ยังค้างการทำงานอยู่นั้นจะหยุดการทำงานลงและเปลี่ยนไปสนใจลูกบอลสีแดงแทนทันที ทำให้ในท้ายที่สุดเราก็จะไม่ได้ค่าผลลัพธ์ของลูกบอลสีเขียว แต่จะได้ของลูกบอลสีแดงเท่านั้น (เพราะลูกบอลสีเขียวไม่สามารถทำจนจบการทำงานได้เพราะมีลูกบอลสีแดงเข้ามาแย่ง)

ในส่วนของ Use Case ที่สามารถเอาไปใช้งานได้

use case ที่สามารถเอาไปใช้ได้ด้วย switchMap

Use case ที่สามารถนำเอา switchMap ไปใช้งานได้นั้น คือ

1.เราสามารถนำไปใช้ในกรณีที่มีการค้นหาข้อมูลต่างๆ โดยเมื่อกดค้นหาข้อมูล 1 ครั้งเราอาจะกำหนดให้มีการเรียกไปที่เซิฟเวอร์ (Backed) เพื่อนำข้อมูลกลับมาแสดงที่หน้าเว็บไซต์ของเรา (frontend) เมื่อมีการใช้ switchMap ในขณะที่มีการเรียก request เพื่อข้อข้อมูล จะทำให้ทุกๆครั้งที่เราสั่งค้นหาไปและผลลัพธ์ ณ ปัจจุบันยังไม่มา แต่เราสามารถมั่นใจได้ว่าเราจะได้ข้อมูลที่เป็นล่าสุดเสมอ

2.สามารถเอาไปใช้ทำ Autocomplete search เพื่อใช้ในการค้นหาชื่อหรือข้อมูลได้ โดยอันนี้จะแตกต่างจากแบบแรกเพราะเรานำ swichMap ไปใช้กับ event ที่เกิดขึ้นมากับการพิมพ์ของผู้ใช้งาน ทำให้ข้อมูลที่จะเอามาแสดงด้านล่างนั้นจะเป็นข้อมูลสุดท้ายที่พิมพ์แน่นอน

สำหรับฮีโร่ตัวถัดมานั้นได้แก่

mergeMap

mergeMap (map + mergeAll) : “Slacker” Operator

Doesn’t do anything except for “Mapping & Flattening” an Observable

mergeMap นั้นเกิดจาก operator “map” ที่รวมกับความสามารถ “mergeAll “(รูปแบบ merge ที่เป็นเวอร์ชั่นที่สามารถใช้ chain ใน pipe ได้นั่นเอง)

โดยที่เรียกมันว่าเป็นตัวขี้เกียจหรือ Slacker นั้นเพราะว่านอกจากลักษณะหรือคุณสมบัติในการ map และ Flattening ให้เราแล้ว มันจะไม่ทำอย่างอื่นให้เราอีก ดังนั้นผลลัพธ์ที่จะได้จากการทำงานของมันก็คือ มันจะทำการ subscribe value ทุกตัวที่เข้ามาซึ่งเป็น Observable และทำการคืนค่าของ value ที่อยู่ด้านในออกมาให้อีกทีหนึ่ง ทำให้เรามั่นใจได้ว่าจะได้ค่าของทุกตัวแน่ๆ และไม่ต้องไปทำ subscribe ซ้อนกันใน subscribe อีกรอบเพื่อดึงค่าออกมา 😍

เพื่อให้เข้าใจง่ายขึ้นจากเดิม เราจะทำการยกตัวอย่างเหมือนกับ switchMap โดยตัวคุณเองเป็นหนึ่งในผู้พัฒนาโปรแกรมซึ่งอยู่ในทีมที่มี feature A และ B เข้ามาเหมือนกับกรณีก่อนหน้า แต่สิ่งที่แตกต่างกันก็คือ บอสของคุณได้บอกกับทีมของคุณว่า “สามารถทำควบคู่ไปกันได้ ไม่ได้เร่งรีบอะไร”

สถานการณ์จำลองของทีมกับ feature B ที่เข้ามาแทรก โดยสามารถทำไปพร้อมๆ กันได้

จากภาพจำลองด้านบน เราจะเห็นว่าทีมของคุณนั้นทำงานกับ feature A ไปตามปกติในตอนที่ feature A เข้ามา และในช่วงเวลาที่มี feature B ที่ได้จากบอสเพิ่มขึ้นมารวมถึงคำสั่งที่ได้รับว่าสามารถทำไปควบคู่กันได้ ทีมของคุณก็จะต้องสามารถทำงานทั้งสองชิ้น (A+B) ไปพร้อมๆกันได้ โดยไม่ทิ้งงานใดงานหนึ่งหรือแบบ parallel นั่นเอง

ภาพของการทำงานกับ feature B ที่เข้ามาแทรก โดยสามารถทำไปพร้อมๆ กันได้ (ภาพผลลัพธ์ท้ายสุด)

จะเห็นได้ว่าในท้ายที่สุดเราจะมีทั้ง Requirement ของ A และ B รวมถึงกล่องสีเขียวและสีแดงที่ใช้สื่อว่าทีมของคุณกำลังทำงานอะไรอยู่เป็นทั้งสีเขียวและสีแดงควบคู่กันไปเลย ซึ่งจะแบ่งกันยังไงก็ได้แต่งานทั้งสองต้องได้ทำอย่างต่อเนื่องนะครับ

Coding#2

สำหรับการอธิบายรูปแบบการทำงานของ mergeMap กับเหตุการณ์จำลองตามที่ได้ยกตัวอย่างไปนั้นจะคล้ายๆ กับของ switchMap เพียงแต่ในรอบนี้เราจะมีการเปลี่ยน operator เป็น mergeMap นะครับ

ภาพจำลองการทำงานของ mergeMap กับ feature ที่เข้ามาในทีม

จากภาพจำลองข้างบน เราจะพบว่าทีมของคุณเองก็ทำงานกับ feature A ที่เข้ามาตามปกติและได้ทำไปเรื่อยๆ จนกระทั่งเมื่อมี feature B เข้ามา เราจะเห็นได้ว่ามันจะยังคงทำ A ต่อไปอยู่ โดยจะทำไปควบคู่กันไปทั้ง A และ B โดยเราไม่สามารถกำหนดได้ว่างานไหนจะเสร็จก่อนกันเป็นลักษณะนี้ไปเรื่อยๆ จนกว่าจะจบการทำงานของ feature นั้นๆ

Code ตัวอย่างและผลลัพธ์ตอนท้ายสุดในการทำงานของ mergeMap กับ feature ที่เข้ามาในทีม

โดยเราจะพบว่าผลลัพธ์ของการทำงานด้วย mergeMap นั้นเราจะได้ข้อมูลมาแน่ๆ ครบทุกตัว (ไม่มีตัวไหนหายไปแน่ๆ) แต่การทำงานของมันจะไม่สามารถกำหนดลำดับได้ว่าอยากให้ใครทำเสร็จก่อนหรือทำเสร็จทีหลัง แต่มันจะทำงานไปพร้อมๆกันตลอดจบกว่าจะจบการทำงาน

มาลองดู Marble Diagram ของ mergeMap กันบ้าง

เหมือนกับก่อนหน้าเลยครับ เราจะมี value ที่รับเข้ามา 3 ตัวตามช่วงของเวลา แต่รอบนี้เราจะเปลี่ยนจาก switchMap เป็น mergeMap แทน มาดูผลลัพธ์ที่ได้กันนะครับ

Marble Diagram อธิบายการทำงานของ mergeMap

หลังจากที่ลูกบอลแต่ละลูก (value) ที่รับเข้ามาซึ่งจะต้องผ่าน mergeMap ที่มี logic ในการ transform ของที่เข้ามาให้กลายเป็นสี่เหลี่ยมขนมเปียกปูน

จากรูปจะเห็นได้ว่าเมื่อลูกบอลสีแดงเข้ามาในเวลาที่ลูกบอลสีเขียวยังทำงานไม่เสร็จ ลูกบอลสีเขียวและสีแดงก็ยังคงสามารถทำงานต่อไปได้ โดยในท้ายที่สุดเราจะได้รับค่าของผลลัพธ์จากทั้งสองตัว (ไม่มีตัวไหนหายไป) ซึ่งจะขึ้นอยู่กับกระบวนการว่าใครทำเสร็จก่อนหรือทำเสร็จทีหลัง

ผลลัพธ์ของการใช้ mergeMap จะไม่มีการการันตีลำดับของผลลัพธ์ที่ออกมา แต่เราสามารถมั่นใจได้ว่าเราจะได้ผลลัพธ์ครบทุกตัวแน่นอนครับ

ในส่วนของ Use Case ที่สามารถเอาไปใช้งานได้

use case ที่สามารถเอาไปใช้ได้ด้วย mergeMap

Use case ที่สามารถนำเอา mergeMap ไปใช้งานได้นั้น คือ

1.เราสามารถรวบรวม Data จากแหล่งข้อมูล 2 ตัวให้กลายมาเป็นตัวเดียวกันได้ โดยข้อมูลนั้นอาจจะมาจากหลังบ้านหรือมาจาก event ที่เกิดขึ้นแล้วรวมให้กลายเป็นผลลัพธ์เดียวกันเพื่อนำไปแสดง เช่นตัวอย่างของทีมที่สามารถทำงานทั้งสองไปพร้อมๆ กันได้นั่นเอง

2.ยกตัวอย่างในกรณีที่เรามี feature ที่ใช้ค้นหาข้อมูลอยู่แล้วและอยากได้ข้อมูลบางอย่างจาก API อื่น โดยต้องการนำมาเสริมหรือเพิ่มเติมข้อมูลเก่าให้มีรายละเอียดมากยิ่งขึ้นเราก็สามารถใช้ mergeMap เรียก API นั้นเพื่อขอข้อมูลแล้วทำการ map กับ data ชุดเดิมแล้วค่อยนำไปแสดงผล

3.เราสามารถนำไปใช้ในตอนที่เราอยากได้ค่าด้านในของ observable ที่ return มาเป็น observable โดยตรง เหมือนกับตัวอย่างในบทความที่แล้ว เพื่อนำเอาไปทำอย่างอื่นต่อได้

CheckPoint . .

มาถึงตรงนี้เราก็ได้ทำความรู้จักกับ switchMap และ mergeMap ไปเป็นที่เรียบร้อยกันแล้วนะครับ ถึงตอนนี้ก็ยังเหลือตัวละครอีก 2 ตัวที่ยังรอแนะนำตัวกับเราอยู่ ถ้าพร้อมแล้วก็ตัดสินใจลุยกันต่อที่ concatMap เลยนะครับ 👊

concatMap = map + concatAll()

Waiting for the previous inner Observable to complete before doing the next inner Observable

concatMap นั้นเกิดจาก operator “map” ที่รวมกับความสามารถ “mergeAll “(รูปแบบ concatAll ที่เป็นเวอร์ชั่นที่สามารถใช้ chain ใน pipe ได้นั่นเอง)

โดยความสามารถของมันจะเป็นการทำให้ value ทุกตัวที่รับเข้ามา นอกจากการทำ map และ flattening ให้แล้วมันจะจัดคิวก่อนและหลังให้อีกด้วย เอาเป็นว่าใครมาก่อนได้ทำก่อน ใครมาทีหลังก็ค่อยทำต่อจากคนก่อนหน้า (First come, first served) นั้นเอง ทำให้เราสามารถการันตีเรื่องอันดับก่อนหลังได้แน่ๆ ว่ามันจะเป็นไปตามที่เรากำหนด เรียกได้ว่าเป็นคนจัดคิวให้เรานั้นเอง 😆

ลองยกตัวอย่างเทียบกับเหตุการณ์ก่อนหน้าที่เกิดขึ้นกับทีมของคุณดูกันบ้าง ในรอบนี้บอสของคุณดูใจเย็นขึ้นจากเดิม 😍 และเดินมาบอกกับทีมของคุณว่า “feature B ที่ต้องทำนั้นยังไม่รีบ ทำงานเก่าให้เสร็จแล้วค่อยทำละกัน” ผลลัพธ์ที่เกิดขึ้นก็จะเปลี่ยนไปเป็นตามภาพด้านล่างแทนนะครับ

สถานการณ์จำลองของทีมกับ feature B ที่เข้ามาแทรกโดยให้ทำหลังจากที่ feature A เสร็จแล้ว

โดยเมื่อหลังจากที่มี feature B เข้ามาในขณะที่ A ยังไม่เสร็จเรียบร้อย ทีมของเราก็ไม่ต้องเป็นกังวลอะไร เมื่อไหร่ที่ทำ A เสร็จแล้วถึงค่อยเริ่มทำงานของ feature B แทน ให้มั่นใจก่อนว่างานที่ทำอยู่ต้องเสร็จถึงค่อยไปเริ่มอันใหม่

ภาพของทีมกับ feature B ที่เข้ามาแทรกโดยให้ทำหลังจากที่ feature A เสร็จแล้ว (ภาพผลลัพธ์ท้ายสุด)

ภาพสุดท้ายที่เราได้ก็คือ ทีมจะได้ทำ feature B ก็ต่อเมื่อ A เสร็จเรียบร้อยเท่านั้น โดยที่ถ้ามีงานใหม่เข้ามาแทรกในจังหวะนี้ก็ต้องรอต่อไปเรื่อยๆ ให้ตัวก่อนหน้าเสร็จเรียบร้อยเท่านั้นนะครับ

Coding#3

กลับมาดู Code กันต่อ โดยรอบนี้เราเลือกเอา concatMap มาใช้แทนกันดูนะครับ

ภาพจำลองการทำงานของ concatMap กับ feature ที่เข้ามาในทีม

จากภาพจำลองด้านบน เราจะเห็นว่าเมื่อเราเปลี่ยนมาใช้ concatMap แทน ผลลัพธ์ที่เกิดขึ้นกลายเป็นว่า ทีมของเราจะได้ทำ feature A ไปเรื่อยๆ จนกว่าจะเสร็จครบแล้วค่อยเปลี่ยนไปทำของ feature B ซึ่งถ้ามีตัวใหม่เข้ามาเรื่อยๆ ก็จะกลายเป็นคิวไปเรื่อยๆ ไม่สามารถลัดคิวได้

Code ตัวอย่างและผลลัพธ์ตอนท้ายสุดในการทำงานของ concatMap กับ feature ที่เข้ามาในทีม

มาลองดู Marble Diagram ของ concatMap กันบ้าง

เหมือนกับก่อนหน้านี้นะครับ เราจะมี value เป็นลูกบอลคนละสีเข้ามาตามลำดับของเวลา โดยรอบนี้จะเปลี่ยนไปใช้ concatMap แทน มาดูผลลัพธ์ที่เกิดขึ้นกันนะครับ

Marble Diagram อธิบายการทำงานของ concatMap

จะพบว่าในช่วงที่ลูกบอลสีแดงเข้ามาในขณะที่ลูกบอลสีเขียวยังทำงานไม่เสร็จ ในท้ายที่สุดนั้นกระบวนการทำงานของสีแดงจะถูกสั่งให้รอการทำงานไปก่อน โดยจะเริ่มทำก็ต่อเมื่อลูกบอลสีเขียวทำเสร็จแล้วถึงค่อยทำ เราจึงได้ผลลัพธ์ของทุกตัวแต่จะต้องเรียงตามลำดับกันเสมอนะครับ ดังนั้นถ้าหากต้องการการันตีเรื่องลำดับไว้ใจเจ้าตัวนี้ได้เลยนะครับ 😄

ในส่วนของ Use Case ที่สามารถเอาไปใช้งานได้

use case ที่สามารถเอาไปใช้ได้ด้วย concatMap

Use case ที่สามารถนำเอา concatMap ไปใช้งานได้นั้น คือ

1.ในกรณีที่เราอยากทำให้ฟังก์ชันการค้นหาของเราแสดงผลเป็นไปตามลำดับการค้นหาไล่เรียงกันไป

2.สมมุติว่าเรามีหน้า display ข้อมูลส่วนตัวของพนักงานแต่ละคน แล้วอยากได้รูปโปรไฟล์มาแสดงด้วย โดยรูปที่ดึงมาอยากให้มันเรียงมาตามลำดับเพื่อที่เวลาจะแสดงผลจะได้เอาไปใช้ได้เลยไม่ต้องมาค้นจากชื่อรูปอีกที เราก็สามารถใช้ concatMap เพื่อให้เวลาที่ไปขอข้อมูลรูปนั้นส่งกลับมาเป็นลำดับด้วยเลยทีเดียว

exhaustMap

exhaustMap = map + exhaust()

Completely ignore the new coming Observable, Until that previous Observable complete

exhaustMap นั้นเกิดจาก operator “map” ที่รวมกับความสามารถ “mergeAll “(รูปแบบ exhaust ที่เป็นเวอร์ชั่นที่สามารถใช้ chain ใน pipe ได้)

ตัวของมันเองนั้นเป็น Operator ที่จะไม่สนใจสิ่งอื่นนอกจากสิ่งที่ปัจจุบันทำอยู่ โดยหากตัวมันเองกำลังทำอะไรสักอย่างก่อนหน้าอยู่แล้ว และมีค่า value ใหม่เข้ามา มันจะไม่สนใจสิ่งที่เข้ามานั้นแล้วกลับไปทำสิ่งที่ทำก่อนหน้าต่อ ดังนั้นใครก็ตามที่มารบกวนเวลาทำงานของมัน มันก็จะไม่สนใจเด็ดขาดและจะกลับมาสนใจก็ต่อเมื่อทำงานที่ทำอยู่ในปัจจุบันเสร็จเท่านั้น ซึ่งทำให้เราสามารถโฟกัสกับสิ่งที่ทำอยู่เท่านั้น อินดี้พอตัวเลยนะครับเจ้าตัวนี้ 😆

งั้นลองเทียบกับเหตุการณ์จำลองก่อนหน้าดูกันครับว่า หลังจากที่ได้เปลี่ยนมาใช้เจ้าตัวนี้กันแล้วผลลัพธ์มันจะเป็นยังไงกันบ้าง

สถานการณ์จำลองของทีมกับ feature B ที่เข้ามาแทรก แล้วทีมยังคงทำงาน feature A ต่อไป

เริ่มต้นหลังจากที่ทีมนั้นกำลังทำงานในส่วนของ feature A อยู่ตามปกติและได้มี feature B เข้ามาแทรก สิ่งที่จะเกิดขึ้นหลังจากนั้นก็คือทีมของคุณเองจะไม่สนใจงานใหม่ที่เข้ามาและยังคงก้มหน้าก้มตาทำงานเดิมต่อไปจนเสร็จ ถึงแม้จะเสร็จครบหมดแล้วก็ตามก็จะไม่มีการย้อนกลับไปทำ feature B ที่เข้ามาเมื่อกี้ด้วยนะครับ กลายเป็นว่าทีมจะได้ทำแค่ feature A เท่านั้น คล้ายๆ กับในความเป็นจริงเราอาจจะตะโกนกลับไปบอกหัวหน้าของคุณว่า “ฉันไม่ว่างอยู่นะ ไม่เห็นหรอ” แล้วก็กลับมาทำงานเดิมต่อ ซึ่งใครสามารถทำได้จริงนี่น่านับถือจริงๆ นะครับ 😆😆

ภาพของทีมกับ feature B ที่เข้ามาแทรก แล้วทีมยังคงทำงาน feature A ต่อไป (ภาพผลลัพธ์ท้ายสุด)

Coding#4

รอบนี้เรามาลองดู exhaustMap แบบ Coding กันต่อนะครับ ว่าในสถานการณ์นี้มันจะทำงานยังไงกันนะ ❓

ภาพจำลองการทำงานของ exhaustMap กับ feature ที่เข้ามาในทีม

เราจะพบว่าหลังจากที่มี feature B เข้ามาแทรกเมื่อตอนทีมกำลังทำ A อยู่นั้น สิ่งที่เกิดขึ้นก็คือ ทีมก็ยังคงทำ A ต่อไปเรื่อยๆโดยไม่สนใจ B ที่เข้ามาใหม่แถมยังทิ้งงานในส่วนของ B ไปเลยด้วย ทำให้ท้ายที่สุดเราจะได้ผลลัพธ์เป็นแค่ในส่วนของ feature A เท่านั้นจนกว่าจะเสร็จการทำงาน

สำหรับ feature B ที่เข้ามาแทรกกลางคันนั้นเหมือนกับหายไปเลยเฉยๆ ไม่มีการนำกลับมาทำใหม่ ถูกตัดออกไปตั้งแต่ที่ทีมรู้ว่ายังคงมีงานค้างอยู่ ดังนั้นตอนที่เราจะนำ Operator นี้ไปใช้จะต้องมั่นใจว่าอยากได้แค่ตัวปัจจุบันเท่านั้น ตัวใหม่ที่เข้ามาแทรกจะต้องไม่ทำงานนะครับ

Code ตัวอย่างและผลลัพธ์ตอนท้ายสุดในการทำงานของ exhaustMap กับ feature ที่เข้ามาในทีม

มาลองดู Marble Diagram ของ exhaustMap กันบ้าง

โดยกำหนดให้ value ที่รับเข้ามาเป็นลูกบอลทั้งสามสีเหมือนกับกรณีก่อนหน้า โดยจะเปลี่ยนแค่ Operator เป็น exhaustMap นะครับ มาดูผลลัพธ์กันนะครับ

Marble Diagram อธิบายการทำงานของ exhaustMap

เราจะพบว่าในช่วงที่ลูกบอลสีแดงเข้ามาในจังหวะที่ลูกบอลสีเขียวยังทำงานไม่เสร็จ ในท้ายที่สุดนั้นเราจะได้รับแค่ผลลัพธ์ของลูกบอลสีเขียวเท่านั้น ซึ่งสีแดงไม่มีแต่การเริ่มทำงานเลยด้วยซ้ำ ที่เป็นแบบนี้เพราะ exhuastMap จะทำการกัน value อื่นที่เข้ามาหากว่าตัวเองนั้นไม่ว่าง ดังนั้นต้องมีการพิจารณาก่อนว่าต้องการให้ process ก่อนหน้านั้นต้องทำงานให้เสร็จเรียบร้อยก่อนถึงค่อยทำตัวถัดไปหรือไม่ ถ้าใช่ตัวนี้คือสิ่งที่ตอบโจทย์สำหรับการนำไปใช้นะครับ

ในส่วนของ Use Case ที่สามารถเอาไปใช้งานได้

use case ที่สามารถเอาไปใช้ได้ด้วย exhaustMap

Use case ที่สามารถนำเอา exhaustMap ไปใช้งานได้นั้น คือ

  1. เราสามารถนำไป implement ร่วมกับ logic ของการ refresh Data โดยเมื่อเรากด ปุ่มหรือเรียก refresh ในกรณีที่เรามีการเรียกไปแล้วครั้งหนึ่ง และมีการเรียกใหม่ทันทีมันจะไม่มีการเรียกซ้ำให้ถ้าอันเก่ามันยัง refresh ไม่เสร็จ
  2. สามารถนำไป implement ร่วมกับหน้าล็อกอิน โดยเมื่อ user กดล็อกอินไปแล้ว และยังรอการ authorize จากหลังบ้านอยู่ ต่อให้ user มีการกดซ้ำหลังจากนั้น ก็จะไม่มีการเรียกไปที่หลังบ้านอีกแน่นอน
  3. การนำเอาไปใช้กับ logic ในตอนที่เรามีการบันทึกค่าไปเซพที่หลังบ้าน แล้วการเซพข้อมูลดังกล่าวมีการทำงานหลายขั้นตอน โดยที่เราอยากมั่นใจว่าจะต้องไม่สามารถเรียกบันทึกอีกครั้งได้ถ้าเกิดว่าการทำงานก่อนหน้านั้นยังไม่สำเร็จ

Summary

สรุปเรื่องราวของฮีโร่ทั้ง 4 ตัว โดยประกอบไปด้วย switchMap, mergeMap, concatMap และ exhaustMap

โดยมีความเหมือนกันในรูปแบบพฤติกรรม ซึ่งพวกเค้าเหล่านี้จะทำการ Mapping และ Flattening value ที่รับเข้ามาและ emit ผลลัพธ์นั้นออกมาให้เรา ซึ่งให้เราทำงานง่ายขึ้นโดยไม่ต้อง subscirbe() ซ้อนๆกัน เหมือนกับที่อธิบายไว้ในบทความแรกนะครับ

สรุปการทำงานของ Mapping & Flatten Operator ใน RxJS

ส่วนเรื่องความแตกต่างของแต่ละตัวนั้นจะแตกต่างกันโดยขึ้นอยู่กับรูปแบบของผลลัพธ์ที่เราอยากจะได้เมื่อเรียกใช้งาน Operator เหล่านี้

switchMap : “สนใจแค่ตัวที่ใหม่ที่สุดและด่วนที่สุดเสมอ” งานหรือสิ่งที่ทำก่อนหน้าจะทิ้งไปเลย ท้ายที่สุดเราจะได้ผลลัพธ์ของ value ตัวที่รับที่เข้าไปล่าสุดเสมอ

mergeMap : “นอกเหนือจากทำ Mapping + Flattening ให้เรา ก็ไม่ทำอะไรเพิ่มให้” ดังนั้นในตอนที่เรา subscribe() รอรับค่าก็มั่นใจได้ว่าเราจะได้รับผลลัพธ์ของ value ทุกตัวที่รับเข้ามาแน่ๆ ซึ่งถ้าจะใช้ตัวนี้ต้องไม่สนใจเรื่อง order ของผลลัพธ์ด้วยนะครับ

concatMap : “ใครมาก่อนทำก่อน ใครมาหลังจะทำทีหลัง เรียงกันมาเป็นคิวให้” ดังนั้นผลลัพธ์ที่ได้จะมีเรื่องของ ลำดับก่อนหลังเข้ามาด้วย ถ้าเราได้รับ value เข้ามาตามลำดับเราก็สามารถมั่นใจได้เลยว่าผลลัพธ์ที่ได้จะออกมาเป็นลำดับตามนั้นเสมอ

exhaustMap : “ห้ามรบกวน สนใจแค่ตัวปัจจุบัน” จะไม่สนใจ value ที่รับเข้ามาใหม่ ถ้าหากว่ายังทำงานเก่าไม่เสร็จ ต่อให้รับเข้ามากี่ตัวก็ตาม ดังนั้นถ้าเราอยากให้ตัวปัจจุบันต้องทำงานเสร็จก่อนแน่ๆ โดยไม่สนใจตัวใหม่ที่เข้ามาก็สามารถใช้ operator นี้ได้เลย

Final

หลังจากที่ได้ลองนำเอาไปใช้งานบ้างแล้วหรือตอนที่ทดลองด้วยตัวเอง ในความเห็นส่วนตัวของผมเองพบว่า Rx มันสามารถช่วยให้เราสามารถเขียน Logic ออกมาได้สวยงามมากขึ้น สั้นและกระชับอย่างน่าแปลกใจ

โดยความสามารถของมันจริงๆ คือ การที่มันเข้ามาช่วยให้เราสามารถจัดการปัญหาที่เป็นเรื่องของเวลาในแบบที่เราต้องการได้ง่ายขึ้น ในกรณีที่เราต้องการจัดการ event หรือ data ที่รับเข้ามา โดยต้องยุ่งเกี่ยวกับเรื่องของเวลาว่าต้องเป็นไปตามช่วงของเวลา หรือในกรณีที่เกิดเหตุการณ์อยากให้ทำอันนี้เมื่ออันนี้เกิดก่อนหรือเกิดหลัง หรือเมื่ออยากให้เครียร์ค่าบางอย่าง จนถึงการจัดการกับ request ที่เข้ามาหลายๆ ตัวพร้อมกันแล้วค่อยรวมผลลัพธ์เพื่อนำไปแสดงที่หน้าจอ และอื่นๆ อีกหลายอย่าง โดยปัจจุบันนี้นอกจากจะมี RxJS ที่เป็นของ JS เองแล้วก็ได้มีการนำไปพัฒนาร่วมกับภาษาอื่นๆ อย่างแพร่หลายและเป็นที่นิยมมากขึ้นเรื่อยๆ ซึ่งสามารถเห็นได้จาก conference ของต่างประเทศที่มีเพิ่มขึ้นมา โดยสำหรับคนที่สนใจเกี่ยวกับเรื่องนี้นะครับ ตอนนี้เราก็จะมีกลุ่ม RxJS Thailand ที่ไว้สำหรับแลกเปลี่ยนความรู้กันและพูดคุยเกี่ยวกับ Rx โดยสามารถมาเข้าร่วมกันได้นะครับ

ท้ายที่สุดนี้ขอขอบคุณพี่เจมส์ Siwat Kaolueng ที่ให้โอกาสแชร์เนื้อหาเหล่านี้ในงาน ReactiveX Bangkok Meetup first และขอบคุณพี่เก๋ Phatcharaphan Ananpreechakun ​ที่ช่วยกันเตรียมสไลด์และซ้อมกันจนจบงานได้สำเร็จ

โดยด้านล่างนี้คือเครดิตข้อมูลที่ผมได้รวบรวมมาในขณะเตรียมพูดในงาน หากสนใจอยากอ่านเพิ่มเติม สามารถเข้าไปอ่านได้ที่ลิงก์ด้านล่างได้นะครับ

Slide ของ @Michael_Hladky . ในงาน NG-MY
Function Programming ที่อธิบายได้เข้าใจง่ายของ Anjana Vakil — JSUnconf

--

--