[Rx.share()] - Part2 - share มาจากไหน?

Nutron
3 min readJun 30, 2018

--

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

ในบทความที่แล้ว เราได้พูดถึงการทำงานภาพรวมของ share() กันไปแล้วว่ามีมันพฤติกรรมอย่างไร และเรายังได้กล่าวถึงการทำงานของ publsih() และ connect() ควบคู่ไปด้วยเพื่อให้เห็นภาพการทำงานของ share() ได้ชัดเจนขึ้น ในบทความนี้จึงอยากจะมาเจาะลึกถึงการทำงานของ share() เพื่อดูว่ามี Operators อะไรบ้างที่เกี่ยวข้อง ซึ่งจะขอไล่ไปทีละตัวดังนี้

ConnectableObservable

ก่อนอื่นต้องขอทวนเจ้าตัวนี้ก่อน เพราะมันคือกุญแจไปสู่ทุก Operators ที่จะกล่าวถึงหลังจากนี้ โดยเจ้า ConnectableObservable คือ Observable ที่จะไม่ปล่อย item ใดๆ เมื่อถูก subscribe แต่จะปล่อย item ก็ต่อเมื่อมีการเรียกคำสั่ง connect เกิดขึ้น ซึ่งพฤติกรรมนี้จะแตกต่างจาก Observable ปกติทั่วไปที่จะเริ่มทำงานทันทีเมื่อมีการ subscribe เกิดขึ้น

Publish

publish() คือ operator ที่ใช้เปลี่ยน Observable ธรรมดาๆให้กลายเป็น ConnectableObservable ซึ่งเราได้อธิบายการทำงานของมันไปแล้วในบทความที่แล้ว

Connect

connect() คือ Operator ที่ใช้คู่กับ ConnectableObservable ซึ่งหากเปรียบ ConnectableObservable เหมือนแทงค์น้ำแล้วละก้อ เจ้า connect ก็เปรียบเสมือนวาล์วปล่อยน้ำนั่นเอง โดย ConnectableObservable จะเริ่มปล่อย item ออกเมื่อเราสั่งคำสั่ง connect() ลองทวนตัวอย่างเดิมกันอีกสักรอบ

Output:
Observable: Proceed at time: 1518431395795
Observer1: Got result of time: 1518431395795
Observer2: Got result of time: 1518431395795

จะเห็นว่า Observable จะทำงานแค่ครั้งเดียวแล้วแชร์ข้อมูลที่ได้ออกไปให้กับ Observer ทั้งสองตัว

autoConnect

autoConnect() เป็น Operator ที่เป็นขั้น advance ของ connect() โดยแทนที่เราจะต้องเรียกคำสั่ง connect() เพื่อให้ ConnectableObservable เริ่มปล่อย Item แต่ autoConnect() จะทำงานต่างกันนิดหน่อยตรงที่จะเริ่มปล่อย item ทันทีเมื่อมีคนมา subscribe ซึ่งพฤติกรรมจะคล้ายกับการทำงานของ Observable แบบปกติทั่วไป แต่จะต่างกันตรงที่ Observable จะทำงานแค่ครั้งเดียวเมื่อเกิดการ subscribe

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

จากโค้ดตัวอย่างการใช้ connect() ด้านบนหากเราลองเปลี่ยนมาใช้ autoConnect() จะได้โค้ดหน้าตาประมาณนี้

Output:
Observable: Proceed at time: 1518431395795
Observer1: Got result of time: 1518431395795

จากโค้ดจะเห็นว่าเราไม่จำเป็นต้องเรียกคำสั่ง connect() ในตอนท้ายอีกแล้ว แต่ผลลัพธ์ที่ได้จะไม่มี log ของObserver2 เกิดขึ้น เนื่องจากการ subscribe ของ Observer1 จะ trigger ให้ Observable ทำงานทันที ทำให้ Observer2 เข้ามา subscribe ไม่ทัน จึงทำให้ไม่ได้รับ Item ที่ปล่อยออกมานั่นเอง (เหมือน share() ไหมละครับ)

refCount

refCount() เป็นอีกหนึ่ง operator ที่เราต้องรู้จักและเป็นตัวแปลสำคัญในการอธิบายการทำงานของ share() โดย refCount() คือ Operator ที่ทำงานเหมือนกับ autoConnect() นั่นคือ หากมีการ subscribe เกิดขึ้น ConnectableObservable จะเริ่มปล่อย item ทันที

Output:
Observable: Proceed at time: 1518680270882
Observer1: Got result of time: 1518680270882

จากโค้ดจะเห็นว่า ผมลัพธ์ที่ได้ไม่ต่างจากการใช้งาน autoConnect() ซึ่งเจ้า autoConnect() ก็ดันดูเหมือนกับ share() ซะด้วยสิ!

แล้ว refCount() ต่างยังไงกับ autoConnect() อย่างไร?

autoConnect Vs refCount

ถึงแม้จุดเริ่มต้นการทำงานของ autoConnect() และ refCount() อาจจะเหมือนกัน แต่ตอนจบมันไม่เหมือนกันครับ

refCount() จะเรียก connect() ให้ทันทีที่มีคนมา subscribe เหมือน autoConnect() แต่จะทำงานต่างจาก autoConnect() ตรงที่ ConnectableObservable จะหยุดการทำงานทันทีเมื่อไม่มีใคร subscribe มันอยู่ ในขณะที่ autoConnect() นั้น ConnectableObservable ยังคงทำงานต่อไป ถึงแม้จะไม่มีใคร subscribe มันแล้วก็ตาม

ลองดูตัวอย่างโค้ดด้านล่างประกอบครับ

subscribe1: output: 0                              
subscribe1: output: 1
subscribe1: output: 2
postDelayed: dispose 'subscribe1' after 3 seconds
postDelayed: Subscribing again after 5 seconds
subscribe2: output: 5
subscribe2: output: 6
subscribe2: output: 7
subscribe2: output: 8

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

ทีนี้เรามาลองดูผลลัพธ์ของการใช้ refCount() กับบ้าง โดยใช้ code ชุดเดียวกับ เพียงแค่เปลี่ยนจาก autoConnect() มาเป็น RefCount()

val intervalObs = Observable.interval(1, 1, TimeUnit.SECONDS)
.publish().refCount()

ซึ่งผลลัพธ์ที่ได้จะออกมาหน้าตาประมาณนี้ครับ

subscribe1: output: 0                             
subscribe1: output: 1
subscribe1: output: 2
postDelayed: dispose 'subscribe1' after 3 seconds
postDelayed: Subscribing again after 5 seconds
subscribe2: output: 0
subscribe2: output: 1
subscribe2: output: 2
subscribe2: output: 3
subscribe2: output: 4

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

Share() isEqualTo publish().refCount() == true

หากเรากดเข้าไปดูโค้ดของ share() สิ่งที่เราจะเจอคือ จริงๆแล้ว share() ถูกสร้างขึ้นมาจาก publish().refCount() นั่นเอง

แล้วจะมี share() ทำไมใน เมื่อใช้ publish().refCount() ได้ คำตอบคือ เวลาพิมพ์มันสั่นกว่าไหมละครับ และชื่อฟังก์ชั่น share() ก็ฟังดูเป็นอะไรที่เข้าใจง่ายและชัดเจนกว่า

Warning!!!

จากตัวอย่างด้านบนผู้อ่านเห็นอะไรที่น่าพึงระวังของการ ใช้ connect() และ autoConnect() ไหมครับ?

ใช่แล้วครับ Observable มันไม่ตาย! หรือไม่หยุดทำงานนั่นเอง ซึ่งเป็นสิ่งที่เราควรจะระวังเป็นพิเศษ เมื่อต้องการใช้งาน connect() และ autoConnect() เพราะเรากำลังสร้าง Hot Observable ขึ้น ซึ่งถ้าหากใครงงว่า Hot Observable คืออะไรให้ไปอ่านต่อที่นี่ครับ

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

Conclusion

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

สุดท้ายนี้ก็เช่นเคยครับ หากผู้อ่านคิดว่าบทความนี้มีประโยชน์ ก็ฝาก share() และกด 👏 เป็นกำลังใจให้ด้วยนะครับ

--

--