ES6-Promise从入门到实战(AJax和fetch)
Promise定义
A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.
Promise是一个对象,用作占位符,用于延迟(可能是异步)计算的最终结果。
一个 Promise 就是一个对象,它代表了一个异步操作的最终完成或者失败。
本质上,Promise 是一个绑定了回调的对象,而不是将回调传进函数内部。
Promise State
Promise只会处在以下状态之一:
-
Pending(待处理): promise初始化的状态,正在运行,既没有完成也没有失败的状态,此状态可以提升为fulfilled 和 rejected状态
-
Fulfilled(已完成): 如果回调函数实现Promise的
resolve
(回调函数),那么state变为fulfilled
。 -
Rejected(已拒绝): 如果Promise调用过程中遭到拒绝或发生异常,state就会处于
rejected
状态。 -
Settled(不变的): Promise从
pending
状态提升后,状态要么变为fulfilled
,要么变为rejected
,没有其他情况。
即:
Promise 状态转移:
1. pending ---> fulfilled ---> settled
2. pending ---> rejected ---> settled
Promise 状态转移
情景模式
一次苏洵在家宴客,限以冷、香二字为联,并先出一联为“水向石边流出冷,风从花里过来香。”苏轼当场吟出一联:“拂石坐来衣带冷,踏花归去马蹄香。”
对对子的套路很简单,就是一人出上联,一人对下联。
function up() {
console.log('水向石边流出冷,风从花里过来香。');
return '水向石边流出冷,风从花里过来香。';
}
function down() {
console.log('拂石坐来衣带冷,踏花归去马蹄香。');
return '拂石坐来衣带冷,踏花归去马蹄香。';
}
function match () {
// 给1s钟出上联
setTimeout(up, 1000);
// 给2s钟对下联
setTimeout(down, 1000);
}
match(); // 水向石边流出冷,风从花里过来香。
//拂石坐来衣带冷,踏花归去马蹄香。
上面的例子很简单,down
函数总是需要在up
函数之后执行。
如果我们用Promise来实现如下。
const match = new Promise((resolve, reject) => {
// 苏洵说
console.log('苏洵:\n游戏规则:限以冷、香二字为联');
const time = 1000;
setTimeout(() => {
console.log(`苏洵:\n我出上联,且容我思考${time / 1000}s钟!`);
resolve(time);
}, time);
}).then(value => {
console.log(`苏洵:\n经过${value / 1000}s钟思考,我的上联是:`);
console.log('水向石边流出冷,风从花里过来香。');
return value * 2;
}).then((value) => {
console.log('宾客:\n这上联出的有水平呀!');
console.log(`宾客:且容我思考${value / 1000}s钟!`);
return value / 4;
}).then(value => {
console.log(`苏轼:经过${value / 1000}s钟思考,我的下联是:`);
console.log('拂石坐来衣带冷,踏花归去马蹄香。');
console.log(`宾客:。。。`);
}).then(() => {
console.log('犬子对的妙啊,不愧我儿!nice!')
});
上述例子输出结果:
苏洵:
游戏规则:限以冷、香二字为联
undefined
苏洵:
我出上联,且容我思考1s钟!
苏洵:
经过1s钟思考,我的上联是:
水向石边流出冷,风从花里过来香。
宾客:
这上联出的有水平呀!
宾客:且容我思考2s钟!
苏轼:经过0.5s钟思考,我的下联是:
拂石坐来衣带冷,踏花归去马蹄香。
宾客:。。。
犬子对的妙啊,不愧我儿!nice!
不懂,没关系!啥,你说你不懂对对子,没关系, 下面我们来具体讲讲Promise的API。
1.Promise实现——(resolve, reject) 方法
如:foo()和bar()函数都实现promise
function foo() {return new Promise((resolve, reject) => {
resolve();
})}
function bar() {return new Promise((resolve, reject) => {
resolve();
})}
// 使用
foo()
.then( res => bar() ) // bar() returns a Promise
.then( res => {
console.log('Both done')
})
2.Promise.then(onFulfilled, onRejected) 方法
Promise的then()方法允许我们在任务完成后或拒绝失败后执行任务,该任务可以是基于另外一个事件或基于回调的异步操作。
Promise的then()方法接收两个参数,即onFulfilled
和 onRejected
的回调,如果Promise对象完成,则执行onFulfilled回调,如果执行异常或失败执行onRejected回调。
简单的来说,onFulfilled回调接收一个参数,及所谓的未来的值,同样 onRejected 也接收一个参数,显示拒绝的原因。
(ajaxCallPromise
后面有实现这个方法)
ajaxCallPromise('http://example.com/page1').then(
successData => { console.log('Request was successful') },
failData => { console.log('Request failed' + failData) }
)
如果请求过程失败,第二个函数将会执行输出而不是第一个函数输出。
3.Promise.catch(onRejected)方法
除了上述then()
方法可以处理错误和异常,使用Promise的catch()方法也能实现同样的功能,这个方法其实并没有什么特别,只是更容易理解而已。
catch()
方法只接收一个参数,及onRejected
回调。catch()方法的onRejected回调的调用方式与then()方法的onRejected回调相同。
ajaxCallPromise('http://example.com/page1')
.then(successData => console.log('Request was successful'))
.catch(failData => console.log('Request failed' + failData));
4.Promise.resolve(value)方法
Promise的resolve()方法接收成功返回值并返回一个Promise对象,用于未来值的传递,将值传递给.then(onFulfilled, onRejected)
的onFulfilled
回调中。resolve()方法可以用于将未来值转化成Promise对象,下面的一段代码演示了如何使用Promise.resolve()
方法:
const p1 = Promise.resolve(4);
p1.then(function(value){
console.log(value);
}); //passed a promise object
Promise.resolve(p1).then(function(value){
console.log(value);
});
Promise.resolve({name: "Eden"})
.then(function(value){
console.log(value.name);
});
// 输出
// 4
// 4
// Eden
5.Promise.reject(value)方法
Promise.reject(value)
方法与上述Promise.resolve(value)
类似,唯一不同的是将值传递给.then(onFulfilled, onRejected)
的onRejected
回调中,同时Promise.reject(value)
主要用来进行调试
,而不是将值转换成Promise对象。
const p1 = Promise.reject(4);
p1.then(null, function(value){
console.log(value);
});
Promise.reject({name: "Eden"})
.then(null, function(value){
console.log(value.name);
});
// 4
// Eden
6.Promise.all(iterable) 方法
Promise.all(iterable)
方法可以传入一个可迭代Promise对象
(比如数组)并返回一个Promise对象
,当所有的Promise迭代对象成功返回后,整个Promise才能返回成功状态的值。
const p1 = new Promise(function(resolve, reject){
setTimeout(function(){
resolve();
}, 1000);
});
const p2 = new Promise(function(resolve, reject){
setTimeout(function(){
resolve();
}, 2000);
});
// 使用
const arr = [p1, p2];
Promise.all(arr).then(function(){
console.log("Done"); //"Done" is logged after 2 seconds
});
特别需要注意的一点,只要迭代数组中,只要任意一个进入失败状态,那么该方法返回的对象也会进入失败状态,并将那个进入失败状态的错误信息作为自己的错误信息。
const p1 = new Promise(function(resolve, reject){
setTimeout(function(){
reject("Error"); // 返回错误信息
}, 1000);
});
const p2 = new Promise(function(resolve, reject){
setTimeout(function(){
resolve();
}, 2000);
});
const arr = [p1, p2];
Promise.all(arr).then(null, function(reason){
console.log(reason); //"Error" is logged after 1 second
});
7.Promise.race(iterable) 方法
与Promise.all(iterable)
不同的是,Promise.race(iterable)
虽然也接收包含若干个Promise对象的可迭代对象,不同的是这个方法会监听所有的Promise对象
,并等待其中的第一个进入完成或失败状态的Promise对象,一旦有Promise对象满足,整个Promise对象将返回这个Promise对象的成功状态或失败状态,下面的示例展示了返回第一个成功状态的值:
var p1 = new Promise(function(resolve, reject){
setTimeout(function(){
resolve("Fulfillment Value 1");
}, 1000);
});
var p2 = new Promise(function(resolve, reject){
setTimeout(function(){
resolve("fulfillment Value 2");
}, 2000);
});
var arr = [p1, p2];
Promise.race(arr).then(function(value){
console.log(value); //Output "Fulfillment value 1"
}, function(reason){
console.log(reason);
});
接下来我们来使用Promise。
Ajax使用Promise
先实现一个原生的Ajax:
const Ajax = {
get: function(url, fn) {
// XMLHttpRequest对象用于在后台与服务器交换数据
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
// readyState == 4说明请求已完成
if (xhr.readyState === 4 && xhr.status === 200 || xhr.status === 304) {
// 从服务器获得数据
fn.call(this, xhr.responseText);
}
};
xhr.send();
},
// datat应为'a=a1&b=b1'这种字符串格式,在jq里如果data为对象会自动将对象转成这种字符串格式
post: function (url, data, fn) {
const xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
// 添加http头,发送信息至服务器时内容编码类型
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 304)) {
fn.call(this, xhr.responseText);
}
};
xhr.send(data);
}
}
下面使用Ajax请求数据:
const ajaxCallPromise = url => {
return new Promise((resolve, reject) => {
// DO YOUR ASYNC STUFF HERE
Ajax.get(url, function(data) {
if(data.resCode === 200) {
resolve(data.message)
} else {
reject(data.error)
}
})
})
};
// 使用:
ajaxCallPromise('http://example.com/page1')
.then( response1 => ajaxCallPromise('http://example.com/page2'+response1) )
.then( response2 => ajaxCallPromise('http://example.com/page3'+response2) )
.then( response3 => console.log(response3) )
- 首先定义ajaxCallPromise返回类型为Promise,这意味我们会实现一个Promise对象。
- Promise接受两个函数参数,resolve(成功实现承诺)和reject(异常导致失败)
- resolve和reject这两个特有的方法,会获取对应成功或失败的值
- 如果接口请求一切正常,我们将会接收到resolve函数返回的值
- 如果接口请求失败,我们将会接收到reject返回的的值
fetch默认使用Promise
fetch返回一个包含响应结果的promise(一个 Response 对象)对象。
fetch('http://example.com/movies.json')
// 现将请求转为json
.then(function(response) {
return response.json();
})
// 在控制台打印出json
.then(function(myJson) {
console.log(myJson);
});
// 上传图片,处理错误
fetch('flowers.jpg').then(function(response) {
if(response.ok) {
return response.blob();
}
throw new Error('Network response was not ok.');
}).then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
}).catch(function(error) {
console.log('There has been a problem with your fetch operation: ', error.message);
});
fetch
优势:
- 1.语法简洁,更加语义化
- 2.基于标准 Promise 实现,支持 async/await
- 3.同构方便,使用 isomorphic-fetch
fetch
的确很好用,但是有的浏览器确是undefined(比如万恶的IE),这时就需要我们手动实现ajax作为替代,或者引入polyfill。
fetch
原生支持率不高,但是引入下面这些 polyfill 后可以完美支持 IE8+:
- 1.由于 IE8 是 ES3,需要引入 ES5 的 polyfill: es5-shim, es5-sham
- 2.引入 Promise 的 polyfill: es6-promise
- 3.引入 fetch 探测库:fetch-detector
- 4.引入 fetch 的 polyfill: fetch-ie8
- 5.可选:如果你还使用了 jsonp,引入fetch-jsonp
- 6.可选:开启 Babel 的 runtime 模式,现在就使用 async/await