zoukankan      html  css  js  c++  java
  • javaScript Promise 入门

    Promise是JavaScript的异步编程模式,为繁重的异步回调带来了福音。

    一直以来,JavaScript处理异步都是以callback的方式,假设需要进行一个异步队列,执行起来如下:

    animate (ball1, 100, function () {
        animate (ball2, 200, function () {
            animate (ball3, 300, function () {
                animate (ball3, 150, function () {
                    animate (ball2, 150, function () {
                        animate (ball1, 150, function () {
                                // do something
                           })
                      })
                 })
            })
       })

    这就是所谓的回调金字塔,当你想突然增加一个时,你要去算括号,维护起来相当麻烦。

    但如果换成以下这种形式:

    promiseAnimate(ball1,100)
                .then(function(){
                    return promiseAnimate(ball2,200)
                })
                .then(function(){
                    return promiseAnimate(ball3,300)
                })
                .then(function(){
                    return promiseAnimate(ball3,150)
                })
                .then(function(){
                    return promiseAnimate(ball2,150)
                })
                .then(function(){
                    return promiseAnimate(ball1,150)
                })

    看起来就清晰,舒服多了,而且随意插入,无需顾忌。

    接下来,说说这货怎么用。

    1.1 Promise分三类

    1.Constructor

    Promise类似于 XMLHttpRequest,从构造函数 Promise 来创建一个新建新promise对象作为接口。

    要想创建一个promise对象、可以使用new来调用Promise的构造器来进行实例化。

    var promise = new Promise(function(resolve, reject) {
        // 异步处理
        // 处理结束后、调用resolve 或 reject
    });

    2.Instance Method

    对通过new生成的promise对象为了设置其值在 resolve(成功) / reject(失败)时调用的回调函数 可以使用promise.then() 实例方法。

    promise.then(onFulfilled, onRejected)
    resolve(成功)时

    onFulfilled 会被调用

    reject(失败)时

    onRejected 会被调用

    onFulfilledonRejected 两个都为可选参数。

    promise.then 成功和失败时都可以使用。 另外在只想对异常进行处理时可以采用 promise.then(undefined, onRejected) 这种方式,只指定reject时的回调函数即可。 不过这种情况下 promise.catch(onRejected) 应该是个更好的选择。

    promise.catch(onRejected)

    3.Static Method

    像 Promise 这样的全局对象还拥有一些静态方法。

    包括 Promise.all() 还有 Promise.resolve() 等在内,主要都是一些对Promise进行操作的辅助方法。

    看到上面,如果不懂可以忽略,后面在看回来看。

    接下来,举一个栗子:

    function asyncFunction() { 
        return new Promise(function (resolve, reject) {
            setTimeout(function () {
                resolve('Async Hello world');
            }, 16);
        });
    }
    
    asyncFunction().then(function (value) {
        console.log(value);    // => 'Async Hello world'
    }).catch(function (error) {
        console.log(error);  // =>如果出错,将打印错误
    });
    
    //new Promise构造器之后,会返回一个promise对象
    //为promise对象用设置 .then 调用返回值时的回调函数。

    asyncFunction 这个函数会返回promise对象, 对于这个promise对象,我们调用它的 then 方法来设置resolve后的回调函数, catch 方法来设置发生错误时的回调函数。(相当于正确执行then,错误执行catch)

    上面的处理还可以写成第二类的形式:

    asyncFunction().then(function (value) {
        console.log(value);  // => 'Async Hello world'
    }, function (error) {
        console.log(error);  // =>'错误执行这里'
    });

    1.2 Promise的状态:

      "has-resolution" - Fulfilled

      resolve(成功)时。此时会调用 onFulfilled

      "has-rejection" - Rejected

      reject(失败)时。此时会调用 onRejected

      "unresolved" - Pending

      既不是resolve也不是reject的状态。也就是promise对象刚被创建后的初始化状态等

    初始化后,正确执行,错误执行。

    1.3 编写Promise代码

    1.创建Promise对象 

    1. new Promise(fn) 返回一个promise对象

    2. fn 中指定异步等处理

      • 处理结果正常的话,调用resolve(处理结果值)

      • 处理结果错误的话,调用reject(Error对象)

    2.编写Promise对象处理方法

    XXX.then(function onFulfilled(){ //XXX 返回Promise对象     
      //正确执行   
    }).
    catch(function onRejected (error){     
     //错误执行   
    })

    例子如下:

    function getURL(URL) {
        return new Promise(function (resolve, reject) {
            var req = new XMLHttpRequest();
            req.open('GET', URL, true);
            req.onload = function () {
                if (req.status === 200) {
                    resolve(req.responseText);
                } else {
                    reject(new Error(req.statusText));
                }
            };
            req.onerror = function () {
                reject(new Error(req.statusText));
            };
            req.send();
        });
    }
    // 运行示例
    var URL = "http://httpbin.org/get";
    getURL(URL).then(function onFulfilled(value){
        console.log(value);
    }).catch(function onRejected(error){
        console.error(error);
    });
    
    //可以在 Google的console 环境下运行

    2.实战Promise(Promise.resolve 和 Promise.reject)

    2.1 Promise.resolve

    一般我们会用 new Promise() 创建对象

    例如:

    new Promise(function(resolve){
        resolve(42);
    });

    但可以用 以下的方法 简化:

    Promise.resolve(42);

    在这段代码中的 resolve(42); 会让这个promise对象立即进入确定(即resolved)状态,并将 42 传递给后面then里所指定的 onFulfilled 函数。

    方法 Promise.resolve(value); 的返回值也是一个promise对象,所以我们可以像下面那样接着对其返回值进行 .then 调用。

    Promise.resolve(42).then(function(value){
        console.log(value); // =>42
    });

    2.2 Promise.reject

    对应前面的用法,我们可以变通一下

    new Promise(function(resolve,reject){
        reject(new Error("出错了"));
    });

    简化:

    Promise.reject(new Error("出错了"))

    这段代码的功能是调用该promise对象通过then指定的 onRejected 函数,并将错误(Error)对象传递给这个 onRejected 函数。

    Promise.reject(new Error("BOOM!")).catch(function(error){
        console.error(error);
    });

    2.3 Promise#then

    大家已经学会了 .then().catch() 的写法,如果方法链变得更长:

    function taskA() {
        console.log("Task A");
    }
    function taskB() {
        console.log("Task B");
    }
    function onRejected(error) {
        console.log("Catch Error: A or B", error);
    }
    function finalTask() {
        console.log("Final Task");
    }
    
    var promise = Promise.resolve();
    promise
        .then(taskA)
        .then(taskB)
        .catch(onRejected)
        .then(finalTask);
    // Task A
    // Task B
    // Final Task

    taskA() taskB() finalTask() 顺利执行  因为A B没有发生错误,所以没有被执行到。  如果将 taskB() 故意写成错误的

    function taskA() {
        console.log("Task A");
    }
    function taskB() {
        console.log1111("Task B");
    }
    function onRejected(error) {
        console.log("Catch Error: A or B", error);
    }
    function finalTask() {
        console.log("Final Task");
    }
    
    var promise = Promise.resolve();
    promise
        .then(taskA)
        .then(taskB)
        .catch(onRejected)
        .then(finalTask);
    // Task A
    // Catch Error: A or B,TypeError: Object [object Object] has no method 'log11'
    // Final Task

    taskA()正常  onRejected抛出A或B产生的错误

    运行示意图

    2.3.1 Promise 传值

    想要传值,只需要return 示例:

    function doubleUp(value) {
        return value * 2;
    }
    function increment(value) {
        return value + 1;
    }
    function output(value) {
        console.log(value);// => (1 + 1) * 2
    }
    
    var promise = Promise.resolve(1);
    promise
        .then(increment)
        .then(doubleUp)
        .then(output)
        .catch(function(error){
            // promise chain中出现异常的时候会被调用
            console.error(error);
        });

    这段代码的入口函数是 Promise.resolve(1); ,整体的promise chain执行流程如下所示。

    1. Promise.resolve(1); 传递 1 给 increment 函数

    2. 函数 increment 对接收的参数进行 +1 操作并返回(通过return

    3. 这时参数变为2,并再次传给 doubleUp 函数

    4. 最后在函数 output 中打印结果

    每个方法中 return 的值不仅只局限于字符串或者数值类型,也可以是对象或者promise对象等复杂类型。

    return的值会由 Promise.resolve(return的返回值); 进行相应的包装处理,因此不管回调函数中会返回一个什么样的值,最终 then 的结果都是返回一个新创建的promise对象

    2.4 Promise#Catch

    这里的Promise#catch是 promise.then(undefined, onRejected); 方法的一个别名而已,用法如下:

    var promise = Promise.reject(new Error("message"));
    promise.catch(function (error) {
        console.error(error);
    });

    但是在IE8及以下版本则会出现 identifier not found 的语法错误,因为catch是保留字有关。

    解决方法有两种:

    1.使用[]代替.

    var promise = Promise.reject(new Error("message"));
    promise["catch"](function (error) {
        console.error(error);
    });

    2.使用原始写法

    var promise = Promise.reject(new Error("message"));
    promise.then(undefined, function (error) {
        console.error(error);
    })

    2.5 每次调用then都会返回一个新创建的promise对象

     then 或 catch 方法每次都会创建并返回一个新的promise对象 先看例子:

    // 1: 对同一个promise对象同时调用 `then` 方法
    var aPromise = new Promise(function (resolve) {
        resolve(100);
    });
    aPromise.then(function (value) {
        return value * 2;
    });
    aPromise.then(function (value) {
        return value * 2;
    });
    aPromise.then(function (value) {
        console.log("1: " + value); // => 100
    })
    
    // vs
    
    // 2: 对 `then` 进行 promise chain 方式进行调用
    var bPromise = new Promise(function (resolve) {
        resolve(100);
    });
    bPromise.then(function (value) {
        return value * 2;
    }).then(function (value) {
        return value * 2;
    }).then(function (value) {
        console.log("2: " + value); // => 100 * 2 * 2
    });

    第1种写法中并没有使用promise的方法链方式,这在Promise中是应该极力避免的写法。这种写法中的 then 调用几乎是在同时开始执行的,而且传给每个 then 方法的 value 值都是 100 。

    第2中写法则采用了方法链的方式将多个 then 方法调用串连在了一起,各函数也会严格按照 resolve → then → then → then 的顺序执行,并且传给每个 then 方法的 value 的值都是前一个promise对象通过 return 返回的值。

    ✘ then 的错误使用方法
    function badAsyncCall() {
        var promise = Promise.resolve();
        promise.then(function() {
            // 任意处理
            return newVar;
        });
        return promise;
    }

    这种写法有很多问题,首先在 promise.then 中产生的异常不会被外部捕获,此外,也不能得到 then 的返回值,即使其有返回值。

    由于每次 promise.then 调用都会返回一个新创建的promise对象,因此需要像上述方式2那样,采用promise chain的方式将调用进行链式化,修改后的代码如下所示。

    then 返回返回新创建的promise对象
    function anAsyncCall() {
        var promise = Promise.resolve();
        return promise.then(function() {
            // 任意处理
            return newVar;
        });
    }

    2.6 Promise.all

    Promise.all 接收一个 promise对象的数组作为参数,当这个数组里的所有promise对象全部变为resolve或reject状态的时候,它才会去调用 .then 方法

    首先是一个没有使用.all 的例子:

    function getURL(URL) {
        return new Promise(function (resolve, reject) {
            var req = new XMLHttpRequest();
            req.open('GET', URL, true);
            req.onload = function () {
                if (req.status === 200) {
                    resolve(req.responseText);
                } else {
                    reject(new Error(req.statusText));
                }
            };
            req.onerror = function () {
                reject(new Error(req.statusText));
            };
            req.send();
        });
    }
    //  发送XHR请求
    var request = {
            comment: function getComment() {
                return getURL('http://azu.github.io/promises-book/json/comment.json').then(JSON.parse);
            },
            people: function getPeople() {
                return getURL('http://azu.github.io/promises-book/json/people.json').then(JSON.parse);
            }
        };
    function main() {
        function recordValue(results, value) {
            results.push(value);
            return results;
        }
        // [] 用来保存初始化的值
        var pushValue = recordValue.bind(null, []);
        return request.comment().then(pushValue).then(request.people).then(pushValue);
    }
    // 运行的例子
    main().then(function (value) {
        console.log(value);
    }).catch(function(error){
        console.error(error);
    });

    发送两个XHR请求,然后mian方法顺序处理,看到main()里面的写法 估计都是晕晕的。 

    使用.all之后

    function getURL(URL) {
        return new Promise(function (resolve, reject) {
            var req = new XMLHttpRequest();
            req.open('GET', URL, true);
            req.onload = function () {
                if (req.status === 200) {
                    resolve(req.responseText);
                } else {
                    reject(new Error(req.statusText));
                }
            };
            req.onerror = function () {
                reject(new Error(req.statusText));
            };
            req.send();
        });
    }
    var request = {
            comment: function getComment() {
                return getURL('http://azu.github.io/promises-book/json/comment.json').then(JSON.parse);
            },
            people: function getPeople() {
                return getURL('http://azu.github.io/promises-book/json/people.json').then(JSON.parse);
            }
        };
    function main() {
        return Promise.all([request.comment(), request.people()]);
    }
    // 运行示例
    main().then(function (value) {
        console.log(value);
    }).catch(function(error){
        console.log(error);
    });

    mian()里面简洁多了,也很好理解了

    在上面的代码中,request.comment() 和 request.people() 会同时开始执行,而且每个promise的结果(resolve或reject时传递的参数值),和传递给 Promise.all 的promise数组的顺序是一致的。

    main().then(function (results) {
        console.log(results); // 按照[comment, people]的顺序
    });

     以下代码可以看得出.all是同时执行的:

    // `delay`毫秒后执行resolve
    function timerPromisefy(delay) {
        return new Promise(function (resolve) {
            setTimeout(function () {
                resolve(delay);
            }, delay);
        });
    }
    var startDate = Date.now();
    // 所有promise变为resolve后程序退出
    Promise.all([
        timerPromisefy(1),
        timerPromisefy(32),
        timerPromisefy(64),
        timerPromisefy(128)
    ]).then(function (values) {
        console.log(Date.now() - startDate + 'ms');
        // 約128ms
        console.log(values);    // [1,32,64,128]
    });

    这个promise对象数组中所有promise都变为resolve状态的话,至少需要128ms。实际我们计算一下Promise.all 的执行时间的话,它确实是消耗了128ms的时间。

    从上述结果可以看出,传递给 Promise.all 的promise并不是一个个的顺序执行的,而是同时开始、并行执行的。

    2.7 Promise.race

    Promise.race 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理

    // `delay`毫秒后执行resolve
    function timerPromisefy(delay) {
        return new Promise(function (resolve) {
            setTimeout(function () {
                resolve(delay);
              console.log(delay+',')
            }, delay);
        });
    }
    // 任何一个promise变为resolve或reject 的话程序就停止运行 但不会取消其他Promise的执行
    Promise.race([
        timerPromisefy(1),
        timerPromisefy(32),
        timerPromisefy(64),
        timerPromisefy(128)
    ]).then(function (value) {
        console.log(value);    // => 1, 32,  1  64, 128,
    });

     到了最后,估计您已经对promise模式有了一个比较完整的了解,异步编程会变得越来越重要,在这种情况下,我们需要找到办法来降低复杂度,promise模式就是一个很好的例子。

    以上文章是参考 JavaScript Promise 迷你书,想更加深入的同学请移步这里。

  • 相关阅读:
    墙奸有感
    关于ubuntu里的fcitx
    Ubuntu 9.10 ati HD 3470 显卡驱动 搞定
    XP与Ubuntu双系统的问题
    invalid conversion from ‘__pthread_t*’ to ‘pid_t’
    Julian Day
    m的n次幂的求法
    Sublime Text 2
    在虚拟机Virtualbox安装Win8消费者版
    记一个循环的错误
  • 原文地址:https://www.cnblogs.com/QRL909109/p/5367451.html
Copyright © 2011-2022 走看看