美文网首页
2019-05-18 Rx.js

2019-05-18 Rx.js

作者: tsl1127 | 来源:发表于2019-05-19 19:41 被阅读0次

    通过「换一种思路」来解决「异步」问题
    Rx.js比async还要好

    我们的所有网页应用都是异步的:
    脚本加载
    播放器
    数据访问
    动画
    DOM事件绑定、数据事件绑定

    异步编程


    image.png

    我们可以看到,异步编程中的状态(state)是很难跟踪的


    image.png
    三处用到了movieTicket变量
    当项目变复杂时,你很难理解某个状态是如何变化的。

    另一方面,使用回调时,try...catch 语法基本是没用的


    image.png
    这是异步的,try catch捕获不了

    另外,如果你监听了一个事件却忘了销毁它,很容易造成内存泄露。这在异步编程很常见。


    image.png

    只要按钮不消失,匿名函数就不会消失
    为了解决这些问题,让我们回到 1994 年。1994 年有一本书叫做《设计模式》


    image.png
    这本书讲了很多编程套路(编程套路就是设计模式)
    image.png

    书中介绍的所有设计模式之间的关系
    这里只关注其中的两个设计模式
    Iterator 迭代器
    Observer 观察者

    迭代器
    function makeIterator(array){
    var nextIndex = 0;

    return {
       next: function(){
           return nextIndex < array.length ?
               {value: array[nextIndex++], done: false} :
               {done: true};
       }
    };
    

    }

    var it = makeIterator(['a', 'b']);
    console.log(it.next().value); // 'a'
    console.log(it.next().value); // 'b'
    console.log(it.next().done); // true

    image.png

    ES 6 提供了一个语法糖来达成迭代器模式,这个语法糖叫做生成器(Generator)


    image.png
    image.png

    所谓迭代器模式就是你可以用 .next() API 来「依次」访问下一项。(next只是一个函数名而已,可以随意约定)
    如果有下一项,你就会得到 {value: 下一项的值, done: false}
    如果没有下一项,你就会得到 {value: null, done: true}
    观察者模式
    这个模式则是监听一个对象的变化,一旦对象发生变化,就调用你提供的函数。(JS 已废弃 Object.observe(),请使用 Proxy API 代替)
    var user = {
    id: 0,
    name: 'Brendan Eich',
    title: 'Mr.'
    };

    // 创建用户的greeting
    function updateGreeting() {
    user.greeting = 'Hello, ' + user.title + ' ' + user.name + '!';
    }
    updateGreeting();

    Object.observe(user, function(changes) {
    changes.forEach(function(change) {
    // 当name或title属性改变时, 更新greeting
    if (change.name === 'name' || change.name === 'title') {
    updateGreeting();
    }
    });
    });


    image.png

    两种模式的区别
    假设 A 是一个迭代器,那么 B 可以主动使用 A.next() 来要求 A 产生变化。(B主动要求A变化)
    假设 B 是一个观察者,在观察着 A,那么 A 一旦变化,A 就会主动通知 B。(A变化之后B被动接收通知)

    或者这么说:在观察者模式里,被观察的人在迭代观察者(调用观察者的一个函数)。
    再说清楚一点:观察者就是一个迭代器,被观察的人一旦有变化,就会调用观察者的一个函数。

    user .on change
    observer.next()
    只不过,观察者永远可以 .next(),不会结束。而迭代器是会结束的,即返回 {done: true}

    数组VS事件
    Array: [ {x:1,y:1}, {x:2, y:2}, {x:10,y:10} ]
    Event: {x:1,y:1} ... {x:2, y:2} ... {x:10, y:10}
    数组和事件,有啥区别?

    他们都是 collection(数据集、集合)。

    为了阐述它俩之间的相同点,我们来举两个例子。

    首先我们介绍 Array 的 4 个操作:
    forEach

    [1,2,3].forEach(x=> console.log(x))
    1
    2
    3
    map
    [1,2,3].map(x=> x+1)
    [2,3,4]
    filter
    [1,2,3].filter(x => x>1)
    [2,3]
    concatAll(这不是标准 API,不过很容易实现这个 API)
    [ [1] , [2,3], [], [4] ].concatAll()
    [1,2,3,4]
    用这几个 API 我们可以做一些 amazing 的事情,在 Netflix 我们主要向用户展示一些好看的剧集:
    我们需要展示评分最高的剧集给用户。能不能用上面的操作做到呢?
    let getTopRatedFilms = user =>
    user.videoLists
    .map( videoList =>
    videoList.videos
    .filter( video => video.rating === 5.0)
    ).concatAll()

    getTopRatedFilms(currentUser)
    .forEach(film => console.log(film) )
    好,如果我现在告诉你,一个拖曳操作能用类似的代码实现,你相信吗?
    let getElemenetDrags = el =>
    el.mouseDowns
    .map( mouseDown =>
    document.mouseMoves
    .takeUntil(document.mouseUps)
    )
    .concatAll()

    getElementDrags(div)
    .forEach(position => img.position = position )
    能做到这一切,都是因为 Observable(大意:可被观察的对象)
    Observable
    Observable = Collections + Time


    image.png

    用途
    Observable 可以表示
    事件
    数据请求
    动画
    而且可以方便的把这三种东西组合起来,因此,异步操作变得很简单。

    将事件转化为 Observable 的 API 很简单
    var mouseDowns = Observable.fromEvent(element, 'mouseDown')

    之前我们是如何操作事件的?——监听(或者叫做订阅)
    // 订阅或监听
    let handler = e => console.log(e)
    document.addEventListener('mousemove', handler)
    // 取消订阅或去掉监听
    document.removeEventListener('mousemove', handler)

    现在我们怎么对事件进行操作呢?——forEach

    // 订阅
    let subscription = mouseMoves.forEach(e => console.log(e) )
    // 取消订阅
    subscription.dispose()
    将事件包装成 Observable 对象,可以方便地使用 forEach/map/filter/takeUntil/concatAll 等 API 来操作,比之前的方式容易很多。

    为了处理失败情况,forEach 还可以接收两个额外的参数:


    image.png

    看起来有点像 Promise 对吧。

    为了跟清楚地阐述如何使用 forEach/map/filter/takeUntil/concatAll 等 API 来操作 Observable 对象,我现在发明一种新的语法:


    image.png

    这个语法的规则是

    {1...2} 表示这个对象会一开始发射一个1,一段时间后发射一个2
    {1...2......3}表示发射1,一段时间后发射2,两段时间后发射3(也就是说 ... 表示一段时间,...... 表示两段时间)
    forEach

    {1......2............3}.forEach(console.log)
    1
    一段时间后
    一段时间后
    2
    一段时间后
    一段时间后
    一段时间后
    一段时间后
    3
    map
    {1......2............3}.map(x=>x+1)
    2
    一段时间后
    一段时间后
    3
    一段时间后
    一段时间后
    一段时间后
    一段时间后
    4
    filter
    {1......2............3}.filter(x=>x>1)
    一段时间后
    一段时间后
    2
    一段时间后
    一段时间后
    一段时间后
    一段时间后
    3


    image.png
    image.png
    image.png
    image.png
    image.png

    自动搜索建议


    image.png

    这个 demo 的难点有两个:
    如果用户依次输入 abcdef,请问你应该发送几个请求?答案是用函数防抖,发一次请求。
    如果用户输入 a,然后 300 毫秒后输入 b,那么你会发两个请求,一个请求查询 a 相关的热词,一个请求查询 ab 相关的热词,你能保证这两个请求响应的顺序吗?答案是不能。(竞态问题)

    使用 Observable 来思考这个问题
    let search =
    keyPresses
    .debounce(250) //
    .map(key =>
    getJSON('/search?q=' + input.value)
    .retry(3)
    .takeUntil(keyPresses)
    )
    .concatAll()
    search.forEach(
    results => updateUI(results),
    error => showMessage(error)
    )

    最开始的回调地狱
    最后我们本文回到最开始的代码

    function play(movieId, cancelButton, callback){
    let movieTicket
    let playError
    let tryFinish = () =>{
    if(playError){
    callback(playError)
    }else if(movieTicket && player.initialized){
    callback(null, movieTicket)
    }
    }
    cancelButton.addEventListener('click', ()=>{ playError = 'cancel' })
    if(!player.initialized){
    player.init((error)=>{
    playError = error
    tryFinish()
    })
    }
    authorizeMovie(movieId, (error, ticket)=>{
    playError = error
    movieTicket = ticket
    tryFinish()
    })
    }

    通过改变思维方式,你可以写出这样的代码
    let authorizations =
    player.init()
    .map(()=>
    playAttempts
    .map(movieId=>
    player.authorize(movieId)
    .retry(3)
    .takeUntil(cancels)
    )
    .concatAll()
    )
    .concatAll()

    authorizations.forEach(
    license => player.play(license),
    error => showError(error)
    )
    Rx.js的教程

    相关文章

      网友评论

          本文标题:2019-05-18 Rx.js

          本文链接:https://www.haomeiwen.com/subject/eskxzqtx.html