Promise
对象是用于异步操作的。
Promise
的真正强大之处在于它的多重链式调用,可以避免层层嵌套回调。如果我们在第一次ajax请求后,还要用它返回的结果再次请求呢?
使用Promise
,我们就可以利用then
进行「链式回调」,将异步操作以同步操作的流程表示出来。
以下是个小Demo:
/* e.g */
sendRequest('test1.html', '').then(function(data1) { console.log('第一次请求成功, 这是返回的数据:', data1); return sendRequest('test2.html', data1); }).then(function(data2) { console.log('第二次请求成功, 这是返回的数据:', data2); return sendRequest('test3.html', data2); }).then(function(data3) { console.log('第三次请求成功, 这是返回的数据:', data3); }).catch(function(error) { //用catch捕捉前面的错误 console.log('sorry, 请求失败了, 这是失败信息:', error); });
基本用法
上一小节我们认识了promise
长什么样,但对它用到的resolve
、reject
、then
、catch
想必还不理解。下面我们一步步学习。
Promise
对象代表一个未完成、但预计将来会完成的操作。
它有以下三种状态:
pending
:初始值,不是fulfilled,也不是rejectedfulfilled
:代表操作成功rejected
:代表操作失败
Promise
有两种状态改变的方式,既可以从pending
转变为fulfilled
,也可以从pending
转变为rejected
。一旦状态改变,就「凝固」了,会一直保持这个状态,不会再发生变化。当状态发生变化,promise.then
绑定的函数就会被调用。
注意:Promise
一旦新建就会「立即执行」,无法取消。这也是它的缺点之一。
下面就通过例子进一步讲解。
/* 例3.1 */
//构建Promise
var promise = new Promise(function (resolve, reject) { if (/* 异步操作成功 */) { resolve(data); } else { /* 异步操作失败 */ reject(error); } });
类似构建对象,我们使用new
来构建一个Promise
。Promise
接受一个「函数」作为参数,该函数的两个参数分别是resolve
和reject
。这两个函数就是就是「回调函数」,由JavaScript引擎提供。
resolve
函数的作用:在异步操作成功时调用,并将异步操作的结果,作为参数传递出去; reject
函数的作用:在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then
方法指定resolved
状态和reject
状态的回调函数。
/* 接例3.1 */
promise.then(onFulfilled, onRejected);
promise.then(function(data) {
// do something when success }, function(error) { // do something when failure });
then
方法会返回一个Promise。它有两个参数,分别为Promise从pending
变为fulfilled
和rejected
时的回调函数(第二个参数非必选)。这两个函数都接受Promise对象传出的值作为参数。
简单来说,then
就是定义resolve
和reject
函数的,其resolve
参数相当于:
function resolveFun(data) {
//data为promise传出的值 }
而新建Promise中的'resolve(data)',则相当于执行resolveFun函数。
Promise新建后就会立即执行。而then
方法中指定的回调函数,将在当前脚本所有同步任务执行完才会执行。如下例:
/* 例3.2 */
var promise = new Promise(function(resolve, reject) { console.log('before resolved'); resolve(); console.log('after resolved'); }); promise.then(function() { console.log('resolved'); }); console.log('outer'); -------output------- before resolved after resolved outer resolved
由于resolve
指定的是异步操作成功后的回调函数,它需要等所有同步代码执行后才会执行,因此最后打印'resolved',这个和例2.2是一样的道理。
Promise常见问题
经过上一章的学习,相信大家已经学会使用Promise
。
总结一下创建promise的流程:
- 使用
new Promise(fn)
或者它的快捷方式Promise.resolve()
、Promise.reject()
,返回一个promise对象 - 在
fn
中指定异步的处理
处理结果正常,调用resolve
处理结果错误,调用reject
如果使用ES6的箭头函数,将会使写法更加简单清晰。
这一章节,将会用例子的形式,以说明promise使用过程中的注意点及容易犯的错误。
情景1:reject 和 catch 的区别
- promise.then(onFulfilled, onRejected)
在onFulfilled
中发生异常的话,在onRejected
中是捕获不到这个异常的。 - promise.then(onFulfilled).catch(onRejected)
.then
中产生的异常能在.catch
中捕获
一般情况,还是建议使用第二种,因为能捕获之前的所有异常。当然了,第二种的.catch()
也可以使用.then()
表示,它们本质上是没有区别的,.catch === .then(null, onRejected)
情景2:如果在then中抛错,而没有对错误进行处理(即catch),那么会一直保持reject状态,直到catch了错误
/* 例4.1 */
function taskA() { console.log(x); 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); -------output------- Catch Error: A or B,ReferenceError: x is not defined Final Task
根据例4.1的输出结果及流程图,可以看出,A抛错时,会按照 taskA → onRejected → finalTask这个流程来处理。A抛错后,若没有对它进行处理,如例3.7,状态就会维持rejected
,taskB不会执行,直到catch
了错误。
/* 例4.2 */
function taskA() { console.log(x); console.log("Task A"); } function taskB() { console.log("Task B"); } function onRejectedA(error) { console.log("Catch Error: A", error); } function onRejectedB(error) { console.log("Catch Error: B", error); } function finalTask() { console.log("Final Task"); } var promise = Promise.resolve(); promise .then(taskA) .catch(onRejectedA) .then(taskB) .catch(onRejectedB) .then(finalTask); -------output------- Catch Error: A ReferenceError: x is not defined Task B Final Task
将例4.2与4.1对比,在taskA后多了对A的处理,因此,A抛错时,会按照A会按照 taskA → onRejectedA → taskB → finalTask这个流程来处理,此时taskB是正常执行的。
情景3:每次调用then
都会返回一个新创建的promise对象,而then
内部只是返回的数据
/* 例4.3 */
//方法1:对同一个promise对象同时调用 then 方法
var p1 = new Promise(function (resolve) { resolve(100); }); p1.then(function (value) { return value * 2; }); p1.then(function (value) { return value * 2; }); p1.then(function (value) { console.log("finally: " + value); }); -------output------- finally: 100 //方法2:对 then 进行 promise chain 方式进行调用 var p2 = new Promise(function (resolve) { resolve(100); }); p2.then(function (value) { return value * 2; }).then(function (value) { return value * 2; }).then(function (value) { console.log("finally: " + value); }); -------output------- finally: 400
第一种方法中,then
的调用几乎是同时开始执行的,且传给每个then的value都是100,这种方法应当避免。方法二才是正确的链式调用。
因此容易出现下面的错误写法:
/* 例4.4 */
function badAsyncCall(data) { var promise = Promise.resolve(data); promise.then(function(value) { //do something return value + 1; }); return promise; } badAsyncCall(10).then(function(value) { console.log(value); //想要得到11,实际输出10 }); -------output------- 10
正确的写法应该是:
/* 改写例4.4 */
function goodAsyncCall(data) { var promise = Promise.resolve(data); return promise.then(function(value) { //do something return value + 1; }); } goodAsyncCall(10).then(function(value) { console.log(value); }); -------output------- 11
情景4:在异步回调中抛错,不会被catch
到
// Errors thrown inside asynchronous functions will act like uncaught errors
var promise = new Promise(function(resolve, reject) { setTimeout(function() { throw 'Uncaught Exception!'; }, 1000); }); promise.catch(function(e) { console.log(e); //This is never called });
情景5: promise状态变为resove
或reject
,就凝固了,不会再改变
console.log(1);
new Promise(function (resolve, reject){ reject(); setTimeout(function (){ resolve(); //not called }, 0); }).then(function(){ console.log(2); }, function(){ console.log(3); }); console.log(4); -------output------- 1 4 3