你可能已经知道Promises现在已经是JavaScript标准的一部分了。Chrome 32 beta版本已经实现了基本的Promise API。如今,Promise的概念在web开发中已经不是什么新鲜玩意了。我们中的大多数人已经在一些流行的JS库例如Q、when、RSVP.js中使用过了Promises。即使是jQuery中也有一个和Promises很类似叫做Deferred的东西。但是JavaScript原生支持Promises确实是一件很令人激动高的事情。本文将首先讲述Promises的基础知识并向你展示怎样在你的JS开发中利用好Promises。
注意:目前Promises只是一个实验的特性,目前只有Chrome 32 beta和最新版本的Firefox支持。
Promises综述
一个Promise对象代表一个目前还不可用,但是在未来的某个时间点可以被解析的值。它允许你以一种同步的方式编写异步代码。例如,如果你想要使用Promise API异步调用一个远程的服务器,你需要创建一个代表数据将会在未来由web服务返回的Promise对象。唯一的问题是目前数据还不可用。当请求完成并从服务器返回时数据将变为可用数据。在此期间,Promise对象将扮演一个真实数据的代理角色。接下来,你可以在Promise对象上绑定一个回调函数,一旦真实数据变得可用这个回调函数将会被调用。
Promise的API
正式开始编写代码之前,我们先要用下面的代码创建一个Promise对象:
if(window.Promise){//检查浏览器是否支持Promise
var promise = new Promise(function(resolve,reject){
//在此处编写异步代码
});
}
我们实例化了一个Promise对象并给它传递了一个回调函数。这个回调函数接收两个参数,resolve()和reject(),它们都是函数。你所有的代码都将放在回调函数中,如果一切顺利的话,Promise将会调用resolve(),变为fulfilled状态。万一发生了错误,reject()将会连同一个Error对象被调用,这预示着Promise变为rejected状态。
现在我们编写一个简单的例子来展示Promise的用法。下面的代码对web服务进行了一次异步请求,它将随机返回一个JSON格式的笑话。我们来看看Promise在此处是怎么使用的:
if(window.Promise){
console.log('Promise found');
var promise = new Promise(function(resolve,reject){
var request = new XMLHttpRequest();
request.open('GET','http://api.icndb.com/jokes/random');
request.onload = fucntion(){
if(request.status == 200){
resolve(request.response); //我们在此处获得了数据,因此解析Promise
}else{
reject(Error(request.statusText));//状态码不是200,因此调用reject
}
};
request.onerror = function(){
reject(Error('Error fetching data'));//错误发生,拒绝Promise
};
request.send(); //发送请求
});
console.log('Asynchronous request made.');
promise.then(function(data){
console.log('Got data! Promise fulfilled.');
document.getElementByTagName('body')[0].textContent = JSON.parse(data).value.joke;
},function(error){
console.log('Promise rejected');
console.log(error.message);
});
} else {
console.log('Promise not available');
}
在上面的代码中,Promise()构造函数中的回调函数包含了一段用来从远端服务器获取数据的异步代码。在这里,我们仅仅创建了一个AJAX请求从http://api.icndb.com/jokes/random随机返回一个笑话。当接收到远端服务器返回的JSON回应是,它将传递给resolve()。万一发生了错误,reject()就会连同一个Error对象一起被调用。
当我们实例化了一个Promise对象时,我们得到了一个代理,它表示数据将在未来的某个时间点可以被使用。在上面的例子中,我们期待一些数据将在未来的某个时间点从远端服务器返回。因此,我们怎噩梦知道数据什么时候是可用的呢?这就是为什么要使用Promise.then()的原因。函数then()接收两个参数:一个成功回调和一个失败回调。这些回调函数在Promise处于settled状态(fulfilled或者rejected)时被调用。如果Promise处于fulfilled状态,成功回调函数将会连同你传递给resolve()的数据一起被触发。如果Promise处于rejected状态,失败回调函数将会被调用。无论你给reject()函数传递了什么参数都会传递给这个回调函数。
Promise有三种状态:
- pending(没有fulfilled或者rejected)
- fulfilled
- rejected
Promise.status属性,是代码不可以获取的私有属性,用来表示以上这些创带。一旦一个Promise变为rejected或者fulfilled,状态将会永久的与之关联。这意味着一个Promise只能成功或者失败一次。如果Promise已经处于fulfilled状态了然后你在它上面连同两个回调函数绑定了then()方法,那么成功回调将会被正确的调用。因此,在Promise的世界中,我们并没有兴趣知道Promise什么时候完成。我们只关心Promise的最终输出结果。
链式Promises
有些时候,我们想要将Promises链式组装起来。例如,你可能会有多个回调操作。当一个操作返回给你一个数据是,你将会基于这个数据开始另一个操作,以此类推。下面的代码将会展示怎样进行链式Promises操作:
function getPromise(url){
//返回一个Promise
//发送一个异步请求到url作为Promise的一部分
//在得到结果之后,连同数据解析promise
}
var promise = getPromise('some url here');
promise.then(function(result){
//我们在这里得到结果
return getPromise(result); //在此处再次返回一个Promise
}).then(function(result){
//处理最终结果
});
该部分的技巧在于当你在then()中返回了一个简单的值是,下一个then()将会连同这个返回值被调用。但是如果你在then()中返回了一个Promise,下一个then()将会等到这个Promise完成时才会被调用。
处理错误
以现在已经知道了then()函数接收两个回调函数作为参数。第二个回调函数在Promise变为rejected时被掉哦那个。但是,我们还有一个catch()函数来处理Promise的rejected状态。我们来看看下面的代码:
promise.then(fucntion(result){
console.log('Got data!',relust);
}).catch(function(error){
console.log('Error occurred!',error);
});
它等价于:
promise.then(function(result){
consoe.log('Got data',result);
},function(error){
console.log('Error occurred!',error);
});
需要注意的是如果Promise处于rejected状态并且then()没有一个失败回调函数,那么控制将会移动到下一个拥有失败回调函数的then()或者下一个catch()。除了显式的处理Promise的rejected状态,当Promise()构造器函数回调抛出一个错误时catch()也会被调用。因此,你依然可以使用catch()来实现日志的作用。注意到我们会使用try…catch来处理错误,但是在Promise中因为有了catch()我们没有必要使用这个方法,无论是同步还是异步错误。
总结
在本文汇总我们简要的介绍了JavaScript中新的Promise API。显然它使我们编写代码更加轻松了。我们可以在不知道异步代码将来的返回值时前进。Promise的API还包含很多东西,我们在后面将会一一讲述。