es6 promise与异步编程
对于一些还不具备大量编程经验的朋友来说,promise可能是es6比较难以掌握的点。首先是很多名词,比如Promises,es6 Promise, 回调函数(callback),Promise/A+,异步编程等。下面就首先介绍下这些名词的含义和区别。
所谓异步编程中的异步是相对于同步的概念的。js是单线程的语言,同一时间只能做一件事,为了指定一些稍后要执行的代码,我们需要异步。在客户端,主要的异步方式有事件,setTimeout,Ajax等。Node的发展大大扩展了js语言的边界,我们知道,Node使用非阻塞IO模型,它使用回调函数模式来实现异步编程。比如:
readFile('example.txt', function(err, contents){
if(err){ throw err; }
console.log(contents);
});
console.log('Hi!');
上面代码中readFile的第二个参数就是回调函数。它会在读取完example.txt后被添加到执行队列中。上面代码的执行顺序是--执行readFile函数,在遇到读取文件时暂停,打印"Hi",读取文件结束后将回调添加到作业队列中,执行回调函数,打印contents。
本来呢,使用回调函数是能够完成异步编程的。但是随着代码的逻辑越复杂,这种异步编程方式越来越难以阅读和追踪程序错误,所以发展出了Promises规范来完成异步编程。
Promises是一系列异步编程规范的统称。我们需要了解的是其中的Promise/A+规范。es6通过Promise这个内建对象实现了该规范。所以我们可以使用es6中的Promise对象来进行异步编程。
下面将对es6中的Promise对象进行介绍。至于jQuery中延迟对象$.deferred(),根据规范自己实现promise和ES7的Async/Await异步方式等更多内容,后面会专门写一篇文章进行介绍。
语法
Promise的3种状态
一个promise实例有3种状态,分别是:
- pending -- 挂起,表示Promise结果还未知。
- fulfilled -- 已完成, 表示Promise成功完成。
- rejected -- 已拒绝,表示Promise未成功结束。
promise处于这3种状态中的一种,并且可以由pending状态变为fulfilled状态,或由pending变为rejected状态。反之则不行。
为了便于理解,下面将通过一个生活化的例子,来解释什么是Promise?
Promise是允诺的意思。它就是一个关于未发生的事情的承诺。比如:
你订了一份烧烤,店家说半个小时内送到,这就是一个Promise。现在,这个Promise还没有发生,所以可能半个小时内配送成功或者失败。对此,你预备了两种处理方式:成功 -- 美滋滋的吃烧烤,失败 -- 去楼下店里吃。
在半个小时内,这个Promise处于pending状态,你正常上网,撸代码。一段时间后,这个promise就有了结果。是成功(fulfilled)或者失败(rejected)。根据这个结果,你之前的两种处理方式就会相应执行。这就是promise。
对应的代码如下:
let promise = new Promise(function(resolve, reject){
//等待店家送来中...
let result = '配送成功'? true : false;
if(result){
resolve(value);
}else{
reject(reason);
}
});
promise.then(function(value){
//美滋滋吃烧烤...
//value为上面resolve()中传递的值, 比如共100块钱。
}, function(reason){
//叫上隔壁老王去楼下吃...
//reason为上面reject()的传递的原因,比如烤糊了...
});
上面代码就是通过promise异步编程的代码。这里要注意的是Promise构造函数接收一个函数作为参数,函数内部是异步的逻辑。这个函数接收两个参数:resolve和reject。resolve()可以把promise推向fulfilled状态,reject()可以把promise推向rejected状态。
promise有个then方法,用于处理promise成功或失败后的逻辑。then有两个参数:
参数1为promise成功时执行的函数,该函数的参数value对应于上面resolve(value)中的value值;
参数2为promise失败时执行的函数,该函数的参数reason对应于reject(reason)中的reason值,表示失败的原因。
一旦promise有了结果(成功或失败),就会执行对应then中的函数。
通过Promise处理Ajax的例子
Ajax是客户端最常用的异步编程场景,下面一个例子演示了使用Promise进行Ajax操作的代码。
function getData(method, url){
let promise = new Promise(function(resolve, reject){
let xmlHttp = new XMLHttpRequest();
xmlHttp.open(method, url);
xmlHttp.send();
xmlHttp.onload = function () {
if (this.status == 200 ) {
resolve(this.response);
} else {
reject(this.statusText);
}
};
xmlHttp.onerror = function () {
reject(this.statusText);
};
})
return promise;
}
getData('get','www.xxx.com').then(successFun, failFun);
function successFun(value){
//Ajax成功处理函数...
}
function failFun(reason){
//Ajax失败处理函数...
}
创建一个已决的Promise
前面的例子promise创建时,promise都处于pending状态,根据异步操作的结果将promise推向成功或失败状态。
Promise类型有两个静态方法Promise.resolve(value),Promise.reject(reason)可以分别创建已经是fulfilled和已经是rejected状态的promise。
比如:
let promise = Promise.resolve(44);
promise.then(function(value){
console.log('fulfilled', value);
})
上面代码promise在被创建出来时,已经是fulfilled状态,接下来会直接将then中的回调函数加入到作业队列中,等待作业队列中前面的任务完成后执行该函数。
这里传入Promise.resolve(value)和Promise.reject(reason)中的参数和之前Promise构造是对应的参数是一样的。
Promise.prototype.then()和Promise.prototype.catch()
上面已经演示过promise实例上then方法的用法,每一个promise实例还具有catch方法。
catch()方法只处理reject的情况,他的行为与调用Promise.prototype.then(undefined, onRejected)相同。比如:
let p = new Promise(function(resolve, reject){
//...
reject(new Error('something wrong!'))
})
p.catch(function(reason){
//拒绝
})
上面catch方法中的回调在promise被reject时调用。
then()和catch()的返回值
每次对then()或catch()的调用都会返回另一个promise,这也是很多代码可以写成类似链式调用的原因。比如:
let p1 = new Promise(function(resolve, reject){
resolve(42);
});
let p2 = p1.then(function(value){
console.log(value);
})
p2.then(function(){
console.log("Finished");
}, function(){
console.log('something wrong!');
});
p1 == p2 // false,注意:p1.then()会返回一个新的promise,所以p1与p2并不相等
//可以写成链式调用的形式,比如
p1.then(function(value){
console.log(value)
}).then(function(){
console.log('do something');
}).then(function(){
console.log('Finished');
})
在上面代码中,p1.then()返回了一个promise为p2, 那么p2的状态和p1之间有什么关系呢?
更具体一点说,当p1变为fulfilled时,p1.then()返回的p2是什么状态呢?二者有什么联系呢?
p2的行为与p1.then()中回调函数的返回值有关:
- 如果then中的回调函数抛出一个错误,或者回调函数中调用reject(reason),那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
- 如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
- 如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
- 如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
- 如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。
- 如果then中的回调函数无显式的返回值,并且也没有调用reject(),那么返回的Promise为接收状态。
Promise.all()处理多个promise
Promise内建对象上的静态方法Promise.all()用于处理多个promise的情况。
Promise.all([promise1, promise2,...])返回一个promise的实例,接收一个promise组成的数组为参数。只有当数组内的promise都成功时,才会调用对应的then中的成功处理函数,只要有一个不成功,那么调用对应的拒绝处理函数。
依然使用前面那么订烧烤的例子,你不仅订了烧烤,还在另一家订了啤酒。打算等到烧烤和啤酒都配送成功后一起吃,美滋滋~~。比如:
Promise.all([订烧烤,订啤酒]).then(function(value){
//吃烧烤,喝啤酒...
}, function(reason){
//拒绝的原因,烤糊了或者啤酒卖完了...
})
这里要注意的一点是,对于数组中的promise,只要有任一个promise为拒绝,那么就会立即执行then中的拒绝处理函数,并不会等待其他promise的结果。只有当所有promise的结果都成功时,才执行then中的成功处理函数。比如:
var p1 = new Promise(function(resolve, reject){
setTimeout(function(){
console.log('A');
resolve();
}, 1000)
});
var p2 = Promise.reject(new Error('error'));
var p3 = new Promise(function(resolve, reject){
setTimeout(function(){
console.log('B');
resolve();
}, 0)
});
Promise.all([p1,p2,p3]).then(function(value){
console.log('success!');
}, function(reason){
console.log('failed');
})
//结果为failed B A
由于p2为已拒绝状态的promise,所以Promise.all()立即变为拒绝状态,打印failed,p1和p2会继续执行,但对于Promise.all()的结果没有影响。
Promise.race()处理多个promise
Promise内建对象上的静态方法Promise.race()同样用于处理多个promise的情况。同样返回一个Promise,同样接收一个promise数组作为参数。
与all不同的地方在于,数组中的promise就像在赛跑一样(race),并且只关心第一名的情况,只要有其中一个promise有了结果,Promise.race()的状态就会立即与该promise相同。
数组中其他promise继续执行,但对于Promise.race()的结果没有影响。
注意事项
- 我们构造promise实例的代码是立即执行的,而then方法中的回调函数是异步调用的,在promise的状态变为成功或拒绝时,才会把相应的处理函数添加到promise工作队列中。并且该函数会先于setTimeout执行。例如:
var promise = new Promise(function(resolve, reject){
console.log('A');
resolve('C');
})
console.log('B');
setTimeout(function(){
console.log('D');
},0)
promise.then(function(value){
console.log(value)
});
//打印A, B, C, D
- 如果then方法中传入的参数被忽略,或者是非函数,比如:
p.then(function(value){
//...
})
//或者
p.then(undefined, function(reason){
//...
})
那么,相应的回调处理函数被忽略,then方法返回的promise会保留上一个promise的状态和参数。最典型的例子:
var p = new Promise(function(resolve, reject){
reject(new Error('error'));
})
p.then(function(value){
//...
}).then(function(value){
//...
}).then(undefined, function(reason){
console.log(reason);
})
//打印'error'
p的前两次then调用的拒绝处理函数被忽略,然后reject状态和错误信息就一直往后传递,直到被最后一次then调用捕获。
最佳实践
关于Promise的知识点很多,但是最常用的场景就是Ajax。比如:
function getData(method, url){
var promise = new Promise(function(resolve, reject){
//Ajax获取数据的代码...
if(success){
resolve(response)
}else{
reject(statusText)
}
})
return promise;
}
getData('get','www.xxx.com').then(Fun1).then(Fun2).then(Fun3).catch(function(reason){
//错误处理逻辑...
});
更多关于es6的内容,可以关注右侧我的专栏--学习ES6。
参考:
MDN Javascript Promise.
Promise介绍-基础篇.
《深入理解ES6》-- Promise与异步编程。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。