主要作用
1.用来传递异步操作的消息
2.三种状态:pending、Resolved、Rejected,而且只能从第一种状态转到后两者状态之一。
3.缺点
(1)一旦新建就会立即执行
(2)如果不设置回调函数,Promise内部抛出的错误不会反应到外部
(3)如果处于Pending状态,不知道目前到底进行到哪一个阶段。
4.基本用法
var promise = new Promise(function(resolve,reject){
//...
if(/*异步操作成功*/){
resolve(value);//这个函数是js引擎提供的,不用自己部署,这个value可以传递给then方法的参数
}else{
reject(error);//这个函数是js引擎提供的,不用自己部署,这个value可以传递给then方法的参数
}else{
}
})
接收一个函数作为参数,而这个函数的两个参数也是两个函数,是由js引擎提供,不用自己部署。后一个函数不一定要提供
resolve函数的作用是将pending转为resolved状态,在异步操作成功的时候调用,reject则是将其转为rejected状态,在异步操作失败的时候调用。
then()方法
1.作用:当Promise实例生成以后,可以用then方法分别指定Resolved状态和Rejected状态的回调函数。定义在原型对象Promise.prototype上的,为Promise实例添加状态改变时的回调函数
2.then方法返回的是一个新的Promise实例,因此可以采用链式写法,即then方法后面还可以调用另一个then方法。
//异步加载图片
function loadImageAsync(url){
return new Promise(function(resolve,reject){
var image = new Image();
image.onload = function(){
resolve(image);
};
image.onerror = function(){
reject(new Error('cound not load image at'+url));
};
image.src = url;
})
}
//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";
clent.setRequestHeader("Accept","application/json");
client.send();
function handler(){
if(this.readyState !== 4){
return;
}
if(this.status === 200){
resolve(this.response);//当转为resolved状态的时候,调用then方法
//就会把this.response这个值传递给then方法参数中的value。
}else{
reject(new Error(this.statusText));
//当promise的状态转为rejected时,就会把这个错误信息传递给then方法
}
};
});
return promise;
}
getJSON("/post.json").then(function(json)){
console.log(json);//这里的json即是上面的this.response
},function(error){
console.log(error);
}
getJSON("/post.json").then(function(json){
return json.post; //这里返回的可能还是一个Promise对象
}).then(function(post)){
//上一个then方法的返回值传递给这个then方法的函数的参数
});
Promise.prototype.catch()
1.这个方法是指定发生错误时的回调函数
注意:如果Promise的状态已经是Resolved,再抛出错误也是无效的。
var promise = new Promise(function(resolve,reject){
resolve("ok");//因为这里已经是resolved状态了,下面再抛出错误也无效
throw new Error('test');
})
promise.then(function(value){
console.log(value);
}).catch(function(error){
console.log(error);
})
//ok这里只会打印ok
还有要注意的是:
(1)Promise对象的错误具有“冒泡”性质,一直会向后面传递,直到捕获为止。不过一般来说,不要在then方法中定义Rejected状态的回调函数,最好在外面用catch方法
(2)还有和传统的try...catch块不同,如果没有使用catch方法来指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。
(3)如果在Promise内部,状态已经是resolved了,再抛出错误,这个错误也就是在Promise函数体外抛出的。原因是此时Promise的函数体已经运行结束。
(4)catch方法返回的还是一个Promise对象,因此后面还可以紧接着调用then方法。
有两种创建Promise对象实例以及调用它的方法
//第一种,直接利用new命令,创建一个Promise实例
var promise = new Promise(function(resolve,reject){
//...
})
promise.then(function(value){
//...
})
//第二种,用一个函数,然后在其中用return返回一个Promise实例,
var someAsyncThing = function(){
return new Promise(function(resolve,reject){
//下面一行会报错
resolve(x+2);
})
}
someAsyncThing().catch(function(error){
console.log('oh no',error);
}).then(function(){
console.log('carry on');
});
//oh no [ReferenceError: x is not defined]
//carry on
创建Promise实例和then方法和catch方法之间的关系
这里只是我的个人理解:
1.当创建了Promise实例的时候,就会在其中调用两个函数:resolve()和reject(),当resolve()函数成功执行的时候,Promise对象的状态就变成了Resolved,此时就调用then方法,同样的,reject()函数成功执行的时候,Promise对象的状态就变成了Rejected此时也可以调用then方法,但是处理错误的时候,一般推荐使用catch方法。
2.当在创建实例的过程中,会调用resolve()方法,如果这个方法的后面还有throw命令或者抛出错误,此时得判断这个错误是由Promise函数体内部还是外部抛出的,如果是内部抛出,有无catch都无所谓,如果是外部错误,没有catch的话,就会报错(Uncaught Error)。
3.还有在创建实例的过程中,resolve方法的参数,如果是值,就会传递给then方法的函数的参数value。如果还是一个Promise对象,就会返回另一个异步操作。
还有几个小的还很实际的方法
1.Promise.all():用于将多个Promise实例包装成一个新的Promise实例。
参数:可以接受一个数组,但是这个数组的所有成员都是Promise对象的实例,如果不是,就会先调用Promise.resolve方法,将参数转为Promise实例。
也不一定是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例
2.Promise.race():同样是将多个Promise实例包装成一个新的Promise实例。
var p = Promise.race([p1,p2,p3]);
上面的代码中,只要p1,p2,p3中有一个实例率先改变,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就会传递给p的回调函数。同样如果不是Promise实例,就会调用resolve方法来转化为实例。
var p = Promise.race([
fetch('/resource-that-may-a-while'),new Promise(function(resolve,reject){
setTimeout(()=>reject(new Error('request timeout')),5000)
})
])
p.then(response => console.log(response));
p.catch(error => console.log(error));
//上面的代码,如果5秒之内fetch方法无法返回结果,变量p的状态就会变成Rejected,从而触发 catch方法指定的回调函数
3.Promise.resolve()
这个方法就是得到一个Promise对象。
参数:可以是值,也可以是具有then方法的对象
(1)如果是值,如字符串,则返回一个新的Promise对象,那么对象的状态变为resolved,而且参数也会同时传递给回调函数。
(2)其实也可以允许调用的时候,不带参数
4.Promise.reject():也会返回一个新的Promise实例,状态为Rejected。而且reason这个参数会被传递给实例的回调函数。
两个有用的附加方法
ES6的Promise API提供的方法不是很多,可以自己部署一些有用的方法,下面部署两个不在ES6中但很有用的方法。
1.done():这个是为了避免Promise对象的回调链,不管以then方法还是catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误冒泡到全局)。
所以,我们提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();
Promise.prototype.done = function(onFulfilled,onRejected){
this.then(onFulfilled,onRejected)
.catch(function(reason){
//抛出一个全局错误
setTimeout(() => {throw reason},0);
});
}
由上面可以看出,done方法可以像then方法那样使用,也可以不提供任何参数,但是不管怎么样,done方法都会捕捉到任何可能出现的错误,并向全局抛出。
2.finally()方法:用于指定不管Promise对象最后状态如何都会执行的操作。它和done方法的最大区别在于,它接受一个普通的回调函数作为参数,该函数不管怎么样都必须执行
下面是一个例子:服务器使用Promise处理请求,然后使用finally方法关掉服务器
server.listen(0)
.then(function(){
//run test
})
.finally(server.stop);
Promise.prototype.finally = function(callback){
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),//调用resolve函数之后返回一个Promise对象,然后再调用then方法
reason => P.resolve(callback()).then(()=> {throw reason})
)
}//㐀Promis对象的 状态如何,都会执行回调函数callback
应用
1.加载图片:可以将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。
const preloadImage = function(path){
return new Promise(function(resolve,reject){
var image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
})
}
2.Generator函数和Promise的结合
使用Generator函数管理流程,遇到异步操作,通常返回一个Promise对象
//getFoo函数,返回一个Promise对象
function getFoo(){
return new Promise(function(resolve,reject){
resolve('foo');
})
}
var g = function* (){
try{
var foo = yield getFoo(); //执行run(g)的时候,这里会返回一个Promise对象,getFoo()是异步操作
console.log(foo);
}catch(e){
console.log(e);
}
};
//总结一下,调用Generator函数,就会返回一个遍历器对象,代表Generator函数的内部指针,以后每次调用遍历器对象的next方法,就会返回一个包括value和done属性的对象。value的值是yield语句后面那个表达式的值;done属性是一个布尔值。
//通过run方法来包装异步函数
function run(generator){
var it = generator();
function go(result){
if(result.done) return result.value;//这里的result是getFoo函数的返回值,也就是一个Promise对象
return result.value.then(function(value){
return go(it.next(value));//这里不用go执行,直接执行it.next(value)也可以,为什么这里要用go???
},function(error){
return go(it.throw(error));
})
}
go(it.next());//执行it.next()之后就会返回一个包含value和done属性的对象
}
run(g);