Promise 入门与使用

Tags: ECMAScript6

参考资料

Promise最初被提出是在 E语言中, 它是基于并列/并行处理设计的一种编程语言。

概念

ES6规定,Promise对象是一个构造函数,用来生成Promise实例。

规范:Promises -> Promises/A -> Promises/A+

状态

Promise

Promise实例生成以后处于pending状态,可以用then方法分别指定Resolved状态和Reject状态的回调函数。

ES6 Promise 使用

API

  • Promise.prototype.constructor
  • Promise.prototype.then
  • Promise.prototype.catch
  • Promise.all
  • Promise.race
  • resolve
  • reject

基本用法

var promise = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve("success");
    }, 500);
    //reject(error);
});

promise
        .then(function (value) {
            //success
            console.log(value);
            return "over"
        }, function (value) {
            //error
            console.log(value);
        });

console.log("hello");

//"hello"
//"success"

链式调用

then方法执行后会返回另一个promise,它的状态会传递给下一个then,如果没有显示的在回调函数中指定返回的promise对象,那么引擎会帮你生成一个promise并且执行其resolve方法,并且把你的回调函数传入其中。

function asynchData (msg) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve(msg);
        }, 100);
    });
}

asynchData("step1")
        .then(function (value) {
            //success
            console.log(value);
            return asynchData("step2")
        })
        .then(function (value) {
            console.log(value);
        })

//"step1"
//"step2"

asynchData("step2")
        .then(function (value) {
            //success
            console.log(value);
            return "success";
        })
        .then(function (value) {
            console.log(value);
        })
//"step1"
//"success"

本身then、catch方法默认的返回是一个新的Promise对象

function asynchData (msg) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve(msg);
        }, 100);
    });
}

var stepOne = asynchData("stepone");
var stepTwo = stepOne.then(function (val) {
    console.log(val);//stepone
});
var stepThree = stepTwo.then(function (val) {
    console.log(val);//undefined
});
var stepThree2 = stepTwo.then(function (val) {
    console.log(val);//undefined
});
var catchStep = stepThree.catch(function (err) {
    console.log(err);
});

console.log(stepTwo instanceof Promise);//true
console.log(stepThree instanceof Promise);//true
console.log(stepThree2 instanceof Promise);//true
console.log(catchStep instanceof Promise);//true

error处理:catch

其实catch是then(null, function(){...})的别名,实质上是相等的。注意一点,没有使用catch方法指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。所以为了不给自己找麻烦尽量编写catch方法

function asynchData (msg) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            //Promise指定在下一轮“事件循环”再抛出错误,结果由于没有指定catch语句,就冒泡到最外层,成了未捕获的错误。
            throw new Error('async error!');
        }, 1000);

        throw new Error('error!');
    });
}

asynchData("step1")
        .then(function (value) {
            //success
            console.log(value);
        })
        .catch(function (err) {
            console.log(err);
            //如果Promise状态已经变成resolved,再抛出错误是无效的。
            //Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
        });

//"Error: error!"

asynchData("step2")
        .then(function (value) {
            //success
            console.log(value);
        })
        .then(null, function (err) {
            console.log(err);
        });

//"Error: error!"

这里有一个要注意的点:如果你一旦指定catch方法或者你在then方法里指定了reject方法,那么你指定的catch方法会捕获住异常,并且异常不再继续传递下去,无论你在catch里返回什么!除非catch方法里还有报错、或者返回一个reject状态的promise

function asynchData (msg) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            reject(msg);
        }, 100);
    });
}

asynchData("I am error")
        .then(function () {
            //resolve callback
        }, function (err) {
            //reject callback
            console.log(err + "from reject callback")
        })
        .catch(function (err) {
            //err callback
            console.log(err + " from catch callback");//catch callback无法执行,因为上一个then已经指定了reject处理方法
        })

// I am errorfrom reject callback

asynchData("I am error too")
        .then(function () {
            //resolve callback
        }, function (err) {
            //reject callback
            console.log(err + "from reject callback");
            throw new Error("I from reject callback error");
        })
        .catch(function (err) {
            //err callback
            console.log(err + " from catch callback");
            //catch callback会被执行,并且接收的错误是从上一个reject方法返回的
            //上一个then方法抛出异常,then返回一个reject状态的promise,并且被下一个catch方法捕获住
        })

//I am error toofrom reject callback
//Error: I from reject callback error from catch callback

同步or异步

试想下如果让你写一个api,其中有一个参数是callback形式传入,那么方法体内该是以何种形式执行callback呢?是同步呢还是异步执行呢?为了保持API的统一,我们定义callback在方法里都是以异步方式执行的。

关于这个问题,在 Effective JavaScript 的 第67项 不要对异步回调函数进行同步调用 中也有详细介绍。

绝对不能对异步回调函数(即使在数据已经就绪)进行同步调用。

如果对异步回调函数进行同步调用的话,处理顺序可能会与预期不符,可能带来意料之外的后果。

对异步回调函数进行同步调用,还可能导致栈溢出或异常处理错乱等问题。

如果想在将来某时刻调用异步回调函数的话,可以使用 setTimeout 等异步API。

Effective JavaScript
— David Herman

了避免同时使用同步、异步调用可能引起的混乱问题,Promise在规范上规定 Promise只能使用异步调用方式。

var promise = new Promise(function (resolve) {
    console.log("inner promise"); // 1
    resolve(42);
});
promise.then(function (value) {
    console.log(value); // 3
});
console.log("outer promise"); // 2

// inner promise
// outer promise
// 42

Promise保证了每次调用都是以异步方式进行的,所以我们在实际编码中不需要调用 setTimeout 来自己实现异步调用。

多个promise:all

function asynchData (msg, toReject) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            if (toReject) {
                reject(msg + " failure!!!");
            } else {
                resolve(msg);
            }
        }, 100);
    });
}

Promise.all([asynchData("step1"), asynchData("step2"), asynchData("step2")]).then(function (value) {
    console.log(value);//["step1", "step2", "step2"]
});

Promise.all([asynchData("step1", true), asynchData("step2", true), asynchData("step2")]).then(
        function (value) {
            console.log(value);//["step1", "step2", "step2"]
        },
        function (value) {
            console.log(value);//step1 failure!!!
            //之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值
        }
);

处理reject

function asynchData (msg, toReject) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            if (toReject) {
                reject(msg + " failure!!!");
            } else {
                resolve(msg);
            }
        }, 100);
    });
}

//之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数
Promise.race([asynchData("step1"), asynchData("step2"), asynchData("step2")]).then(function (value) {
    console.log(value);//"step1"
});

一些例子

我们定义A、B、C三个方法,并且都是异步的。我们希望A成功后执行B,失败执行C,B失败后执行C。想想改如何实现呢?

function asynchData (msg, toFail) {
    return new Promise(function (resolve, reject) {
        console.log(msg);
        if (toFail) {
            reject();
        } else {
            resolve();
        }
    });
}

asynchData("A")
        .then(function () {
            return asynchData("B");
        })
        .then(null, function () {
            asynchData("C")
        });

这样写是不是就达到目的了?

Deferred & Promise 关系

我们经常见到jquery.Deferred,但是和Promise有什么关系呢?Deferred和Promise不同,它没有共通的规范,每个Library都是根据自己的喜好来实现的。

  • Deferred 拥有 Promise
  • Deferred 具备对 Promise的状态进行操作的特权方法

相信我们使用promise后会给我们带来耳目一新的感觉,减少我们的callback hell的出现频率,大大的增强我们代码的可维护性!

posted @ 2017-03-11 11:13  buzzjan  阅读(472)  评论(0编辑  收藏  举报