Observable 包装

Observable 剖析章节中我们只学到了关键操作符 next()error()complete(),如果是我们自己定义的 Observable 的话,可以使用这些方法来驱动 Observable 。我们还学到了,这些方法会触发相应的回调函数。

用 Observable 包装意味着接收一些非 Observable 的东西并将其转换为 Observable,这样就可以很好的与其它 Observable 配合使用。同样还意味着现在我们可以使用操作符了。

包装 ajax 调用

  1. let stream = Rx.Observable.create((observer) => {
  2. let request = new XMLHttpRequest();
  3. request.open( GET’, url );
  4. request.onload =() =>{
  5. if(request.status === 200) {
  6. observer.next( request.response );
  7. observer.complete();
  8. } else {
  9. observer.error('error happened');
  10. }
  11. }
  12. request.onerror = () => {
  13. observer.error('error happened')
  14. }
  15. request.send();
  16. })
  17. stream.subscribe(
  18. (data) => console.log( data )
  19. )

这里我们需要做的三件事:发出数据错误处理关闭流

发出数据

  1. if(request.status === 200) {
  2. observer.next( request.response ) // 发出数据
  3. }

处理潜在的错误

  1. else {
  2. observer.error('error happened');
  3. }

关闭流

  1. if(request.status === 200) {
  2. observer.next( request.response )
  3. observer.complete() // 关闭流,因为我们不想要更多的数据了
  4. }

包装语音音频 API

  1. console.clear();
  2. const { Observable } = Rx;
  3. const speechRecognition$ = new Observable(observer => {
  4. const speech = new webkitSpeechRecognition();
  5. speech.onresult = (event) => {
  6. observer.next(event);
  7. observer.complete();
  8. };
  9. speech.start();
  10. return () => {
  11. speech.stop();
  12. }
  13. });
  14. const say = (text) => new Observable(observer => {
  15. const utterance = new SpeechSynthesisUtterance(text);
  16. utterance.onend = (e) => {
  17. observer.next(e);
  18. observer.complete();
  19. };
  20. speechSynthesis.speak(utterance);
  21. });
  22. const button = document.querySelector("button");
  23. const heyClick$ = Observable.fromEvent(button, 'click');
  24. heyClick$
  25. .switchMap(e => speechRecognition$)
  26. .map(e => e.results[0][0].transcript)
  27. .map(text => {
  28. switch (text) {
  29. case 'I want':
  30. return 'candy';
  31. case 'hi':
  32. case 'ice ice':
  33. return 'baby';
  34. case 'hello':
  35. return 'Is it me you are looking for';
  36. case 'make me a sandwich':
  37. case 'get me a sandwich':
  38. return 'do it yo damn self';
  39. case 'why are you being so sexist':
  40. return 'you made me that way';
  41. default:
  42. return `I don't understand: "${text}"`;
  43. }
  44. })
  45. .concatMap(say)
  46. .subscribe(e => console.log(e));

语音识别流

这将激活浏览器的麦克风并记录我们的语音

  1. const speechRecognition$ = new Observable(observer => {
  2. const speech = new webkitSpeechRecognition();
  3. speech.onresult = (event) => {
  4. observer.next(event);
  5. observer.complete();
  6. };
  7. speech.start();
  8. return () => {
  9. speech.stop();
  10. }
  11. });

这段代码建立了语音识别 API,然后等待响应,并在响应一次后完成流,很像第一个使用 AJAX 的示例。

注意还定义一个函数用来清理

  1. return () => {
  2. speech.stop();
  3. }

所以我们可以通过调用 speechRecognition.unsubscribe() 来清理系统资源

语音合成, say

函数 say 负责发出你想要表达的语音。

  1. const say = (text) => new Observable(observer => {
  2. const utterance = new SpeechSynthesisUtterance(text);
  3. utterance.onend = (e) => {
  4. observer.next(e);
  5. observer.complete();
  6. };
  7. speechSynthesis.speak(utterance);
  8. });

主体流 hey$

  1. heyClick$
  2. .switchMap(e => speechRecognition$)
  3. .map(e => e.results[0][0].transcript)
  4. .map(text => {
  5. switch (text) {
  6. case 'I want':
  7. return 'candy';
  8. case 'hi':
  9. case 'ice ice':
  10. return 'baby';
  11. case 'hello':
  12. return 'Is it me you are looking for';
  13. case 'make me a sandwich':
  14. case 'get me a sandwich':
  15. return 'do it yo damn self';
  16. case 'why are you being so sexist':
  17. return 'you made me that way';
  18. default:
  19. return `I don't understand: "${text}"`;
  20. }
  21. })
  22. .concatMap(say)
  23. .subscribe(e => console.log(e));

整体逻辑应该是这样的:点击按钮激活 heyClick$speechRecognition$ 监听我们说了什么并把结果发送给 heyClick$ 的转换逻辑,转换逻辑的结果将由 say Observable 发出声音。

这一切归功于 @ladyleet@benlesh

总结

这两个 Observable 包装示例其中一个是简单些的 Ajax,而另一个是有一点点高级的语音 API 。但原理都是相同的: 1)数据是通过调用 next() 来发送的 2)如果没有更多的数据要发送则调用 complete() 3)如果有需要的话,定义一个清理函数可以通过 unsubscribe() 来调用 4)在合适的地方通过调用 error() 来进行错误处理。(只在第一个示例中这样做了)