哎呀,这个东西啊早就了解过了,但是奈何,受回调函数毒害日子太久了,以至于每次到了本可以使用promise来更加优雅的实现逻辑的时候,总是习惯性的使用了回调。这次又回头看了一遍阮老师的es6中关于promise的讲解,决定趁着热乎,记录下来,一来加深自己的理解,二来也提醒自己这是promise的时代了!
以下内容大多数来自阮老师的Es6入门
地址:http://es6.ruanyifeng.com/#docs/promise
什么是promise?
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大,或者说更加的优雅吧,传统写回调函数的方式,一个两个到还好,可以一旦超过两个,往往导致可读性变差,话说当年我也是被回调函数正懵逼过多次,有时候自己写的代码自己回头看都不知道是啥了。。。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
两个特点
- 对象的状态不受外界影响。
既然promise代表的是一个异步的操作,操作就会对应着有状态,promise对象中定义了三个状态分别是:pending(进行中),resolved(已经完成),rejected(已经失败),记住了只有异步操作的结果可以决定当前的promise是处于哪一种状态,任何其他的操作都不能改变这个状态。 - 状态一旦改变,就冻结了
就是说promise对象状态的改变只有两种:从pending到resolved或者是从pending到rejected,只要这其中一种状态发生,状态就凝结了,会一直保持着这个结果。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
缺点
说是缺点,但是我发现这确实我理解promise的一个突破点,那就是promise一旦新建它就会立即执行,无法中途取消,如果不设置回调函数,其内部抛出的错误也无法反应到外部(但是当你不需要捕捉错误的时候你就可以不设置它的回调啊O(∩_∩)O)
基本用法
var promise = new Promise(function(resolve,reject){
//在这里写你的异步代码
if(异步操作成功){
resolve(resp)
}else{
reject(error)
}
})
注意理解这个定义式的例子很重要,Promise是一个构造函数它自身接受一个函数作为参数,而这个参数函数又有两个定义好的参数那就是resolve和reject,千万不要陷入对于这两个函数是从何而来,如何实现之类的迷糊旋涡中,这两个函数是JavaScript引擎提供的不需要自己部署!可以理解为是两个信使或者说是预先提供给你使用的两个trigger(触发器),你可以用resolve触发器来将异步操作成功后的结果传递出去,对应的你可以用reject触发器来将异步操作失败后的错误对象传递出去。同时很重要的一点就是,两个触发器的触发对应着promise状态的改变,resolve使得promise的状态由pending变为resolvereject使得promise的状态由pending变为reject。
好了这样我们就把异步操作的主逻辑写好了,简单概括起来就是用一个promise实例当容器(对它就是个容器,这个比喻太形象了),把异步代码放进去,并自己写好异步代码结束后根据操作结果指定resolve方法和reject方法。然后我们就可以跳出这个构造函数,到外面来用then来接受这个容器传递出来给我们的东西了,因为这个既可能是成功的结果也可能是失败的结果所以理所当然的我们的then方法也接受两个回调函数来做处理,第一个函数是Promise对象状态变为Resolve时候调用的,第二个函数是Promise对象状态变为Reject时候调用的,其中第二个函数是可选的,意味着你可以在then中只写一个回调函数哦,那他就是resolve的时候回调用的(那reject的时候怎么办?不怎么办,啥都不办☺),对就是这么简单!
下面是一个Promise对象的简单例子。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为Resolved,就会触发then方法绑定的回调函数。
Promise 新建后就会立即执行
let promise = new Promise(function(resolve, reject) {
console.log(‘Promise’);
resolve();
});
promise.then(function() {
console.log(‘Resolved.’);
});
console.log(‘Hi!’);
运行结果:
// Promise
// Hi!
// Resolved
上面代码中,Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以Resolved最后输出
下面是一个用Promise对象实现的 Ajax 操作的例子。
var getJSON = function(url) {
var promise = new Promise(function(resolve, reject){
var client = new XMLHttpRequest();
client.open(“GET”, url);
client.onreadystatechange = handler;
client.responseType = “json”;
client.setRequestHeader(“Accept”, “application/json”);
client.send();
function handler() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ’ + json);
}, function(error) {
console.error(‘出错了’, error);
});
上面代码中,getJSON是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。
如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;
resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例,表示异步操作的结果有可能是一个值,也有可能是另一个异步操作,比如像下面这样。
var p1 = new Promise(function (resolve, reject) {
// …
});
var p2 = new Promise(function (resolve, reject) {
// …
resolve(p1);
})
上面代码中,p1和p2都是Promise的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。
注意,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是Pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是Resolved或者Rejected,那么p2的回调函数将会立刻执行。
临近结尾
做个小总结吧,是不是觉得自己反反复复看了好多次的Promise,每次似乎都感觉理解了但是就是不知道怎么用?我来告诉你怎么用
其实没那么复杂,比如你要做一个异步的操作A好了,抄起键盘就敲
var promise = newPromise (function(resolve,reject{
A // 直接在这里写你A的代码就好了
if(A操作成功){
resolve(resp)
}else{
reject(error)
}
}))
ok就酱紫主逻辑就写好了,然后你就可以愉快的在外面使用then来链式的写你的回调函数中的处理逻辑了。
promise.then(function(resp){
this.format(resp)
},function(error){
console.log(error)
})
就这样就好了呀,反复强调注意的一点就是,resolve 和reject是两个定义并实现好的方法,它们有JavaScript引擎提供,你只需要知道异步操作成功就指定resolve方法,操作失败就指定reject方法即可(是不是很傻瓜),不用加this.resolve()或者是this.reject(),直接写resolve(resp)和reject(error)浏览器会执行的,放心去外面用then接受传出来的东西吧。另一点就是可能有些人会记错成一个Promise对应两个then方法,会认为需要两个then方法来分别接一个Promise实例返回出来的成功的回调很失败的回调这个是认识上的大错误,一个Promise只需要一个then方法来接就行了,then方法中直接写好两个回调,排在前面的是成功的回调,排在第二的是失败的回调,then方法后面还可以继续跟好多个then方法那是因为前面的then方法返回的又是一个新的Promise实例,而不是为resolve和reject分别制定两个then。
好了,写完了,关于Promise还有fetchAPI我在GitHub上写了连个炒鸡剪短的实例代码,有兴趣可以去瞅瞅哦,如果有一点点帮到你,是我的荣幸,希望能给我颗小星星哦;
fetch:https://github.com/CoffeeandTea/fetchdemo
fetch+Promise:https://github.com/CoffeeandTea/fetch-promise
flex布局demo:https://github.com/CoffeeandTea/flex-layout
webWorker: https://github.com/CoffeeandTea/Webworker