zoukankan      html  css  js  c++  java
  • 用Promise组织程序

    一、Promise基本用法

    很多文章介绍Promise给的例子是这样的:

    new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open('POST', location.href, true);
        xhr.send(null);
        xhr.addEventListener('readystatechange', function(e){
           if(xhr.readyState === 4) {
               if(xhr.status === 200) {
                   resolve(xhr.responseText);
               } else {
                   reject(xhr);
               }
           }
        })
    }).then(function(txt){
        console.log();
    })

    一定会有小朋友好奇,说尼玛,这不是比回调还恶心?

    这种写法的确是能跑得起来啦……不过,按照Promise的设计初衷,我们编程需要使用的概念并非"Promise对象",而是promise函数,凡是以Promise作为返回值的函数,称为promise函数(我暂且取了这个名字)。所以应该是这样的:

    function doSth() {
        return new Promise(function(resolve, reject) {
            //做点什么异步的事情
            //结束的时候调用 resolve,比如:
            setTimeout(function(){
                resolve(); //这里才是真的返回
            },1000)
        })
    }

    如果你不喜欢这样的写法,还可以使用defer风格的promise

    function doSth2() {
        var defer = Promise.defer();
        //做点什么异步的事情
        //结束的时候调用 defer.resolve,比如:
        setTimeout(function(){
            defer.resolve(); //这里才是真的返回
        },1000)
    
        return defer.promise;
    }

    总之两种是没什么区别啦。

    然后你就可以这么干:

    doSth().then(doSth2).then(doSth);

    这样看起来就顺眼多了吧。

    其实说简单点,promise最大的意义在于把嵌套的回调变成了链式调用(详见第三节,顺序执行),比如以下

    //回调风格
    loadImage(img1,function(){
        loadImage(img2,function(){
            loadImage(img3,function(){
    
            });
        });
    });
    
    //promise风格
    Promise.resolve().then(function(){
        return loadImage(img1);
    }).then(function(){
        return loadImage(img2);
    }).then(function(){
        return loadImage(img3);
    });

    后者嵌套关系更少,在多数人眼中会更易于维护一些。

    二、Promise风格的API

    在去完cssconf回杭州的火车上,我顺手把一些常见的JS和API写成了promise方式:

    function get(uri){
        return http(uri, 'GET', null);
    }
    
    function post(uri,data){
        if(typeof data === 'object' && !(data instanceof String || (FormData && data instanceof FormData))) {
            var params = [];
            for(var p in data) {
                if(data[p] instanceof Array) {
                    for(var i = 0; i < data[p].length; i++) {
                        params.push(encodeURIComponent(p) + '[]=' + encodeURIComponent(data[p][i]));
                    }
                } else {
                    params.push(encodeURIComponent(p) + '=' + encodeURIComponent(data[p]));
                }
            }
            data = params.join('&');
        }
    
    
        return http(uri, 'POST', data || null, {
            "Content-type":"application/x-www-form-urlencoded"
        });
    }
    
    function http(uri,method,data,headers){
        return new Promise(function(resolve, reject) {
            var xhr = new XMLHttpRequest();
            xhr.open(method,uri,true);
            if(headers) {
                for(var p in headers) {
                    xhr.setRequestHeader(p, headers[p]);
                }
            }
            xhr.addEventListener('readystatechange',function(e){
                if(xhr.readyState === 4) {
                    if(String(xhr.status).match(/^2dd$/)) {
                        resolve(xhr.responseText);
                    } else {
                        reject(xhr);
                    }
                }
            });
            xhr.send(data);
        })
    }
    
    function wait(duration){
        return new Promise(function(resolve, reject) {
            setTimeout(resolve,duration);
        })
    }
    
    function waitFor(element,event,useCapture){
        return new Promise(function(resolve, reject) {
            element.addEventListener(event,function listener(event){
                resolve(event)
                this.removeEventListener(event, listener, useCapture);
            },useCapture)
        })
    }
    
    function loadImage(src) {
        return new Promise(function(resolve, reject) {
            var image = new Image;
            image.addEventListener('load',function listener() {
                resolve(image);
                this.removeEventListener('load', listener, useCapture);
            });
            image.src = src;
            image.addEventListener('error',reject);})}function runScript(src){returnnewPromise(function(resolve, reject){var script = document.createElement('script');
            script.src = src;
            script.addEventListener('load',resolve);
            script.addEventListener('error',reject);(document.getElementsByTagName('head')[0]|| document.body || document.documentElement).appendChild(script);})}function domReady(){returnnewPromise(function(resolve, reject){if(document.readyState ==='complete'){
                resolve();}else{
                document.addEventListener('DOMContentLoaded',resolve);}})}

    看到了吗,Promise风格API跟回调风格的API不同,它的参数跟同步的API是一致的,但是它的返回值是个Promise对象,要想得到真正的结果,需要在then的回调里面拿到。

    值得一提的是,下面这样的写法:

    waitFor(document.documentElement,'click').then(function(){
        console.log('document clicked!')
    })

    这样的事件响应思路上是比较新颖的,不同于事件机制,它的事件处理代码仅会执行一次。

    通过这些函数的组合,我们可以更优雅地组织异步代码,请继续往下看。

    三、使用Promise组织异步代码

    函数调用/并行

    Promise.all跟then的配合,可以视为调用部分参数为Promise提供的函数。譬如,我们现在有一个接受三个参数的函数

    function print(a, b, c) {
        console.log(a + b + c);
    }

    现在我们调用print函数,其中a和b是需要异步获取的:

    var c = 10;
    
    print(geta(), getb(), 10); //这是同步的写法
    
    Promise.all([geta(), getb(), 10]).then(print); //这是 primise 的异步写法

    竞争

    如果说Primise.all是promise对象之间的“与”关系,那么Promise.race就是promise对象之间的“或”关系。

    比如,我要实现“点击按钮或者5秒钟之后执行”

    var btn = document.getElementsByTagName('button');
    
    Promise.race(wait(5000), waitFor(btn, click)).then(function(){
        console.log('run!')
    })

    异常处理

    异常处理一直是回调的难题,而promise提供了非常方便的catch方法:

    在一次promise调用中,任何的环节发生reject,都可以在最终的catch中捕获到

    Promise.resolve().then(function(){
        return loadImage(img1);
    }).then(function(){
        return loadImage(img2);
    }).then(function(){
        return loadImage(img3);
    }).catch(function(err){
        //错误处理
    })

    这非常类似于JS的try catch功能。

    复杂流程

    接下来,我们来看比较复杂的情况。

    promise有一种非常重要的特性:then的参数,理论上应该是一个promise函数,而如果你传递的是普通函数,那么默认会把它当做已经resolve了的promise函数。

    这样的特性让我们非常容易把promise风格的函数跟已有代码结合起来。

    为了方便传参数,我们编写一个currying函数,这是函数式编程里面的基本特性,在这里跟promise非常搭,所以就实现一下:

    function currying(){
        var f = arguments[0];
        var args = Array.prototype.slice.call(arguments,1);
        return function(){
            args.push.apply(args,arguments);
            return f.apply(this,args);
        }
    }

    currying会给某个函数"固化"几个参数,并且返回接受剩余参数的函数。比如之前的函数,可以这么玩:

    var print2 = currying(print,11);
    
    print2(2, 3); //得到 11 + 2 + 3 的结果,16
    
    var wait1s = currying(wait,1000);
    
    wait1s().then(function(){
        console.log('after 1s!');
    })

    有了currying,我们就可以愉快地来玩链式调用了,比如以下代码:

    Promise.race([
        domReady().then(currying(wait,5000)), 
        waitFor(btn, click)])
        .then(currying(runScript,'a.js'))
        .then(function(){
            console.log('loaded');
            return Promise.resolve();
        });

    表示“domReady发生5秒或者点击按钮后,加载脚本并执行,完毕后打印loaded”。

    四、总结

    promise作为一个新的API,是几乎完全可polyfill的,这在JS发展中这是很少见的情况,它的API本身没有什么特别的功能,但是它背后代表的编程思路是很有价值的。

    参考地址:http://www.w3ctech.com/topic/721

  • 相关阅读:
    try? try! try do catch try 使用详解
    Swift Write to file 到电脑桌面
    NSLayoutConstraint 使用详解 VFL使用介绍
    automaticallyAdjustsScrollViewInsets 详解
    Swift 给UITableView 写extension 时 报错 does not conform to protocol 'UITableViewDataSource'
    OC Swift中检查代码行数
    Swift中 @objc 使用介绍
    SWift中 '?' must be followed by a call, member lookup, or subscript 错误解决方案
    Swift 中 insetBy(dx: CGFloat, dy: CGFloat) -> CGRect 用法详解
    求1000之内所有“完数”(注:C程序设计(第四版) 谭浩强/著 P141-9)
  • 原文地址:https://www.cnblogs.com/SCOOL/p/4741610.html
Copyright © 2011-2022 走看看