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 迷你书,想更加深入的同学请移步这里。

 

posted @ 2016-04-08 14:18  邱XX  阅读(438)  评论(0编辑  收藏  举报