ทำไมเราต้องใช้ flatMap?


95

ฉันเริ่มใช้ RxJS และไม่เข้าใจว่าทำไมในตัวอย่างนี้เราต้องใช้ฟังก์ชันเช่นflatMapหรือconcatAll; อาร์เรย์ของอาร์เรย์อยู่ที่ไหน

var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseMetastream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseMetastream.subscribe(url => {console.log(url)})

หากมีคนสามารถอธิบายสิ่งที่เกิดขึ้นได้ด้วยสายตาจะเป็นประโยชน์มาก


1
คำตอบนี้ดีมากเนื่องจากมีการอ้างอิงที่มีคุณค่า แต่คำศัพท์ rxjs ไม่สามารถแปลเป็นภาษาอังกฤษได้ดี (ภาพดีกว่า) นั่นเป็นเหตุผลที่ฉันแนะนำให้เรียกใช้ตัวอย่างง่ายๆเช่นตัวอย่างนี้แทนหรือตัวอย่างที่ซับซ้อนกว่าใน rxjs repo และเพิ่มตัวดำเนินการ ".do" ก่อนและหลังแฟลตแมปและตัวดำเนินการแผนที่จากนั้นเพียงตั้งค่าจุดพักด้วยโปรแกรมแก้ไขข้อบกพร่องของ Chrome คุณจะเห็นได้ทันทีว่าแต่ละรายการให้ผลลัพธ์ที่แตกต่างกัน
HipsterZipster

5
ฉันคิดว่าถ้าflatMapจะตั้งชื่อmapThenFlattenก็คงจะสับสนไม่น้อย
แพะ

คำตอบ:


73

เมื่อฉันเริ่มมองRxjsฉันก็สะดุดก้อนหินนั้นด้วย สิ่งที่ช่วยฉันมีดังต่อไปนี้:

  • เอกสารจาก reactivex.io ตัวอย่างเช่นสำหรับflatMap: http://reactivex.io/documentation/operators/flatmap.html
  • เอกสารจาก rxmarbles: http://rxmarbles.com/ คุณจะไม่พบที่flatMapนั่นคุณต้องดูmergeMapแทน (ชื่ออื่น)
  • แนะนำให้รู้จักกับ Rx ที่คุณได้รับขาดหายไป: https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 เป็นตัวอย่างที่คล้ายกันมาก โดยเฉพาะอย่างยิ่งมันกล่าวถึงความจริงที่ว่าคำสัญญานั้นคล้ายกับการเปล่งออกมาเพียงค่าเดียวที่สังเกตได้
  • ในที่สุดก็ดูข้อมูลประเภทจาก RxJava การไม่พิมพ์ Javascript ไม่ได้ช่วยตรงนี้ โดยทั่วไปถ้าObservable<T>หมายถึงวัตถุที่สังเกตซึ่งผลักดันให้ค่าประเภท T แล้วflatMapจะใช้เวลาการทำงานของประเภทเป็นอาร์กิวเมนต์ของตนและผลตอบแทนT' -> Observable<T> ใช้เวลาการทำงานของชนิดและผลตอบแทนObservable<T>mapT' -> TObservable<T>

    กลับไปที่ตัวอย่างของคุณคุณมีฟังก์ชันที่สร้างสัญญาจากสตริง URL ดังนั้นT' : stringและT : promise. และจากสิ่งที่เรากล่าวว่าก่อนpromise : Observable<T''>เพื่อให้มีT : Observable<T''> T'' : htmlหากคุณใส่ฟังก์ชันการสร้างสัญญาmapนั้นคุณจะได้รับObservable<Observable<T''>>เมื่อสิ่งที่คุณต้องการคือObservable<T''>คุณต้องการให้สิ่งที่สังเกตได้เปล่งhtmlค่าออกมา flatMapเรียกว่าอย่างนั้นเพราะมัน flattens (เอาชั้นที่สังเกต) mapผลจาก ทั้งนี้ขึ้นอยู่กับพื้นหลังของคุณนี้อาจจะมีจีนกับคุณ แต่ทุกอย่างกลายเป็นผลึกชัดเจนกับผมมีข้อมูลการพิมพ์และการวาดภาพจากที่นี่: http://reactivex.io/documentation/operators/flatmap.html


2
ฉันลืมที่จะพูดถึงว่าคุณควรจะสามารถลดความซับซ้อนของreturn Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));การreturn jQuery.getJSON(requestUrl);เป็นยังยอมรับฟังก์ชั่นเลือกซึ่งจะส่งกลับฟังก์ชันสัญญาเช่นประเภทflatMap T' -> Promise
user3743222

2
ว้าว GitHub Gist ( gist.github.com/staltz/868e7e9bc2a7b8c1f754 ) นั้นยอดเยี่ยมมาก ฉันแนะนำให้ทุกคนที่ทำงานกับไลบรารี ReactiveX เช่น RxJS
Jacob Stamm

@JacobStamm ฉันเห็นด้วย ทำให้สิ่งต่างๆง่ายขึ้น
CruelEngine

ไวยากรณ์นี้หมายถึงอะไร: T’ -> T? ฉันเข้าใจTว่าเป็นแบบทั่วไป แต่เครื่องหมายอะพอสทรอฟีและลูกศรไม่อ้วนคืออะไร
1252748

คุณสามารถแทนที่ T 'ด้วย X หรือ Y โดยไม่ต้องเปลี่ยนความหมายที่ใดก็ได้ในคำตอบ ลูกศรคือสัญลักษณ์ Haskell สำหรับลายเซ็นประเภท ดังนั้น T '-> T จึงเป็นลายเซ็นสำหรับฟังก์ชันที่รับองค์ประกอบประเภท T' และส่งกลับองค์ประกอบประเภท T
user3743222

124
['a','b','c'].flatMap(function(e) {
    return [e, e+ 'x', e+ 'y',  e+ 'z'  ];
});
//['a', 'ax', 'ay', 'az', 'b', 'bx', 'by', 'bz', 'c', 'cx', 'cy', 'cz']


['a','b','c'].map(function(e) {
    return [e, e+ 'x', e+ 'y',  e+ 'z'  ];
});
//[Array[4], Array[4], Array[4]]

คุณใช้ flatMap เมื่อคุณมี Observable ซึ่งผลลัพธ์เป็น Observables มากกว่า

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

เช่นเดียวกับในตัวอย่างที่สองหากคุณกำลังดำเนินการ async คุณต้องใช้ flatMap

var source = Rx.Observable.interval(100).take(10).map(function(num){
    return num+1
});
source.subscribe(function(e){
    console.log(e)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.1/Rx.min.js"></script>

var source = Rx.Observable.interval(100).take(10).flatMap(function(num){
    return Rx.Observable.timer(100).map(() => num)
});
source.subscribe(function(e){
    console.log(e)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.1/Rx.min.js"></script>


35

ผู้คนมักจะทำสิ่งต่างๆให้ยุ่งยากโดยการให้คำจำกัดความว่า:

flatMap เปลี่ยนไอเท็มที่ปล่อยออกมาจาก Observable ให้เป็น Observables จากนั้นทำให้การปล่อยมลพิษจากสิ่งเหล่านั้นแบนลงเป็น Observable เดียว

ฉันสาบานคำจำกัดความนี้ยังคงทำให้ฉันสับสน แต่ฉันจะอธิบายด้วยวิธีที่ง่ายที่สุดซึ่งก็คือการใช้ตัวอย่าง

สถานการณ์ของเรา : เรามีสิ่งที่สังเกตได้ซึ่งส่งคืนข้อมูล (URL แบบง่าย) ที่เราจะใช้เพื่อทำการเรียก HTTP ซึ่งจะส่งคืนข้อมูลที่สังเกตได้ซึ่งมีข้อมูลที่เราต้องการเพื่อให้คุณสามารถมองเห็นสถานการณ์เช่นนี้:

Observable 1
    |_
       Make Http Call Using Observable 1 Data (returns Observable_2)
            |_
               The Data We Need

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

Observable_1.subscribe((URL) => {
         Http.get(URL).subscribe((Data_We_Need) => {
                  console.log(Data_We_Need);
          });
});

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

ดังนั้นวิธีที่ดีกว่าในการจัดการสิ่งนี้คือการใช้ตัวดำเนินการflatMapซึ่งจะทำสิ่งเดียวกัน แต่ทำให้เราหลีกเลี่ยงการสมัครสมาชิกที่ซ้อนกัน:

Observable_1
    .flatMap(URL => Http.get(URL))
    .subscribe(Data_We_Need => console.log(Data_We_Need));

32

flatMap เปลี่ยนไอเทมที่ปล่อยออกมาโดย Observable ให้เป็น Observables ใหม่จากนั้นทำให้การปล่อยมลพิษจากสิ่งเหล่านั้นแบนลงเป็น Observable ชิ้นเดียว

ตรวจสอบสถานการณ์ด้านล่างที่get("posts")ส่งกลับสังเกตที่เป็น "บี้" flatMapโดย

myObservable.map(e => get("posts")).subscribe(o => console.log(o));
// this would log Observable objects to console.  

myObservable.flatMap(e => get("posts")).subscribe(o => console.log(o));
// this would log posts to console.

2
คำตอบที่ดีและเรียบง่าย ฉันคิดว่านี่อาจจะดีที่สุด
vaughan

"flatMap เปลี่ยนไอเท็มที่ปล่อยออกมาจาก Observable ให้เป็น Observables ใหม่จากนั้นจึงทำให้การปล่อยมลพิษจากสิ่งเหล่านั้นแบนลงเป็น Observable เดียว" นี่คือสิ่งที่ยอดเยี่ยม
MBak


16

ไม่ใช่อาร์เรย์ของอาร์เรย์ มันเป็นสิ่งที่สังเกตได้จากการสังเกต

ต่อไปนี้ส่งคืนสตรีมสตริงที่สังเกตได้

requestStream
  .map(function(requestUrl) {
    return requestUrl;
  });

ในขณะที่สิ่งนี้ส่งคืนกระแสที่สังเกตได้ของ json ที่สังเกตได้

requestStream
  .map(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

flatMap แบนสิ่งที่สังเกตได้โดยอัตโนมัติสำหรับเราเพื่อให้เราสามารถสังเกตสตรีม json ได้โดยตรง


3
เป็นเรื่องยากที่จะเข้าใจแนวคิดนี้โปรดเพิ่มความคิดเห็นเพื่อให้เห็นภาพว่าคุณหมายถึงอะไร "ส่งคืนสตรีม json ที่สังเกตเห็นได้" ขอบคุณ.
user233232

@ user233232 เช่น [x, x, x, x] ถึง [[xxx], [[xxx], [xxx]]]
serkan

กุญแจสำคัญในการทำความเข้าใจประโยคแรกคือการทำความเข้าใจว่าflatMap(และmap) ไม่ได้พิเศษสำหรับอาร์เรย์ เป็นไปได้ที่จะกำหนดการดำเนินการเหล่านี้ในคอนเทนเนอร์ทั่วไปหรือ Wrapper ซึ่งรวมถึงอาร์เรย์พจนานุกรม "optionals" สตรีมปฏิกิริยาคำมั่นสัญญาคำแนะนำและแม้กระทั่งการทำงานของตัวเอง นี่คือคุณสมบัติที่เกิดขึ้นของโครงสร้างทางคณิตศาสตร์ที่เรียกว่าโมนาด ตัวอย่างทั้งหมดข้างต้นเป็นไปตามข้อกำหนดสำหรับการเป็นโมนาดดังนั้นพวกเขาทั้งหมดจึงสามารถให้คำจำกัดความของmapและflatMap(มีข้อแม้บางประการ)
mklbtz

14

เพื่อแสดงการใช้งาน flatMap ที่เทียบเท่าโดยใช้การสมัครสมาชิก

ไม่มีแบนแผนที่:

this.searchField.valueChanges.debounceTime(400)
.subscribe(
  term => this.searchService.search(term)
  .subscribe( results => {
      console.log(results);  
      this.result = results;
    }
  );
);

ด้วย flatMap:

this.searchField.valueChanges.debounceTime(400)
    .flatMap(term => this.searchService.search(term))
    .subscribe(results => {
      console.log(results);
      this.result = results;
    });

http://plnkr.co/edit/BHGmEcdS5eQGX703eRRE?p=preview

หวังว่ามันจะช่วยได้

โอลิวิเยร์.


13

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

เมื่อฟังก์ชันของคุณส่งคืน Observable จะไม่ส่งคืนสตรีม แต่เป็นอินสแตนซ์ของ Observable ตัวflatMapดำเนินการเพียงแค่แมปอินสแตนซ์นั้นกับสตรีม

นั่นคือลักษณะการทำงานของflatMapเมื่อเปรียบเทียบกับmap: ดำเนินการตามฟังก์ชันที่กำหนดและทำให้วัตถุที่เกิดขึ้นแบนราบลงในสตรีม


7

ด้วย flatMap

var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseMetastream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseMetastream.subscribe(json => {console.log(json)})

ไม่มี flatMap

var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseMetastream = requestStream
  .map(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseMetastream.subscribe(jsonStream => {
  jsonStream.subscribe(json => {console.log(json)})
})

0

flatMap เปลี่ยนไอเท็มที่ปล่อยออกมาจาก Observable ให้เป็น Observables จากนั้นทำให้การปล่อยมลพิษจากสิ่งเหล่านั้นแบนลงเป็น Observable เดียว

ฉันไม่ได้โง่ แต่ต้องอ่าน 10 ครั้งนี้และยังไม่เข้าใจ เมื่อฉันอ่านข้อมูลโค้ด:

[1,2,3].map(x => [x, x * 10])
// [[1, 10], [2, 20], [3, 30]]

[1,2,3].flatMap(x => [x, x * 10])
// [1, 10, 2, 20, 3, 30]

จากนั้นฉันก็เข้าใจว่าเกิดอะไรขึ้นมันทำสองสิ่ง:

แบนแผนที่ :

  1. map : transform *) ส่งไอเทมไปยัง Observables
  2. แบน : จากนั้นรวม Observables เหล่านั้นเป็นหนึ่ง Observable

*) คำแปลงร่างบอกว่าไอเทมสามารถเปลี่ยนเป็นอย่างอื่นได้

จากนั้นตัวดำเนินการผสานจะชัดเจนสำหรับมันทำการแฟบโดยไม่ต้องแมป ทำไมไม่เรียกมันว่าmergeMap ? ดูเหมือนว่ายังมีนามแฝงmergeMapที่มีชื่อว่าสำหรับflatMap

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.