Promise对象
promise对象用来管理异步操作(未来某个时刻发生的事),由Promise构造函数生成,有一些内置的api,通过这些api能以同步的代码书写方式来表示异步操作,避免不断嵌套的回调函数的书写方式。可以把Promise对象看做一个容器,里面保存着异步操作的结果。
promise对象用三种状态来表示异步操作的状态:pending(进行中),resolved(已完成),rejected(失败了,抛出错误了),状态只能由pending变成resolved或由pending变成rejected,通过promise对象的api可以(提前)指定当变到resolved或rejected状态时的回调函数,而什么情况下是resolved状态,什么情况下是rejected状态,可以由我们自己决定。
主要api:
1.生成一个primise对象(实例):
var p = new Promise(function(resolve, reject) { // 一些异步操作, 得到操作结果result var result = ... if (result == 'ok') { // 设置这种情况下调用resolved的回调函数 resolve(result) } else { reject('not ok') } })
其中的resolve, reject分别表示resolved和rejected时的回调函数,并且可以传任意参数。如代码所示,当执行resolve(result)
时即是把promise对象的状态变成resolved状态,当执行reject('not ok')
时即是把promise对象的状态变成rejected状态.
2.Promise.prototype.then()设置resolved和rejected的回调函数
p.then(function(val) { console.log(val) // 'ok' }, function (val) { console.log(val) // 'not ok' })
then方法接受两个函数参数,第一个参数就是resolved的回调函数,第二个参数是rejected的回调函数(第二个参数是可选的)
一个简单的例子:
var p = new Promise((resolve, reject)=>{ var result = true var v='aaa ' if(result) { resolve(v) } else { reject(v) } }) p.then( v => console.log(v+'resolve') // 'aaa resolve' , v => console.log(v+'reject') )// 'aaa reject'
一个简单的例子:
console.log(0) var demo = function(){ return new Promise((resolve, reject) => { setTimeout(resolve, 5000, 'aaa ') }) } demo().then( v => console.log(v+'resolve'), v => console.log(v+'reject')) console.log(1) // 运行结果 0 1 (5s后) 'aaa resolve'
一个异步加载图片的简单例子:
function loadImgAsync (url) { return new Promise( (resolve, reject) => { var img = new Image() img.onload = ()=> resolve(img) img.onerror = () => reject(new Error('not ok')) img.src = url }) } var url = 'http://img3.duitang.com/uploads/item/201509/02/20150902131938_yEJVA.jpeg' loadImgAsync(url).then(() => console.log('ok'), err => console.log(err.message) )
.then()方法返回的是一个新的Promise实例,所以可以采用链式写法,.then().then(),前一个then回调函数的return返回值会作为参数传到后一个then的回调函数里,只有当前一个Promise对象状态发生变化才会调用下一个then的方法。
一个简单的例子:
p.then( () => { console.log('resolve1') return 111 }, () => { console.log('reject1') throw "empty" }).then( v => console.log(v+' resolve2'), v => console.log(v+' reject2') ) // 若p执行resolved, 则依次 'resolve1', '111 resolve2' // 若p执行rejected, 则依次 'reject1', '111 reject2' // 若p执行rejected, 同时注释掉throw "empty"或换成 return new Error(),则会依次 'reject1', ' resolve2'!!!
由此可见只有在程序抛出异常或是Promise对象中手动指定调用reject(),只有这两张情况下会执行reject()。
比如: 假设getJson(url)会返回一个Promise对象,用来执行ajax操作,成功时返回结果作为参数传给resolve函数,失败时返回一个Error对象传给reject函数。
getJSON('/post/1.json') .then(post => getJSON(post.commentUrl)) .then( comment => console.log(comment), err => console.log(err) )
上例就是先取post信息,取到后再去取comment信息。
3.Promise.prototype.catch((err)=>{})
异步操作抛出错误时的回调函数,相当于.then(null, rejectFun),错误具有'冒泡'特性,会一直向后传递,所以:
p .then() .then() .catch((err)=>{ // 处理前面三个promise产生的错误 })
一般来说,不推荐p.then(okFun, errorFun), 而应该总是使用p.then(okFun).catch(errorFun), 这种写法更符合同步的写法。注意Promise对象抛出的异常不会传递到外层,所有如果没有.catch(), 即使添加了try{p.then()} catch(err) {},p发生错误时也不会被catch到。
4. Promise.all()
接受一个由Promise实例组成的数组作为参数,同样返回一个Promise实例,相当于把多个Promise实例包装成一个新的Promise实例。如 var p = Promise.all([p1,p2,p3]),只有p1, p2, p3都是resolved,p才会是resolved,此时p1, p2, p3的返回值组成一个数组,传给p的回调函数;只要p1, p2, p3有一个rejected,p就是rejected,此时第一个被rejected的实例的返回值传给p的回调函数。
5. Promise.race([p1, p2,p3])
类似于Promise.all(), 区别是只要p1, p2, p3中有一个实例率先改变状态,p的状态就跟着改变。
6. Promise.resolve()
可以把非Promise对象转为Promise对象,如果已经是Promise对象,则原封不动的返回。
var p = Promise.resolve('hello')
即相当于 var p = new Promise( resolve => resolve('hello'))
,'hello'不是异步操作,状态会立马变成resolved,立即执行回调函数;
var p = Promise.resolve( $.ajax('/a.json') )
即相当于 var p = new Promise( resolve => {vap rsp = $.ajax('/a.json') ;resolve(rsp); )
也可以 var p = Promise.resolve(); p.then()
7. Promise.reject()
类似Promise.resolve(),var p = Promise.reject('hello')
即相当于 var p = new Promise( (resolve, reject) => reject('hello'))
8. async, await关键字
Promise的写法虽然比起普通回调函数的写法有很多改进,但一眼看上去,代码完全是Promise的api(.then().catch()),操作本身的语义还不是特别明显,而用async函数的写法最符合语义,没有与语义不相干的代码。
async关键字表示该函数内部有异步操作,await 后面是一个Promise对象,执行到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
一个简单的例子:
var p = () => {return new Promise( resolve => setTimeout(resolve, 5000))} var fun = async () => { console.log(1) await p().then(()=>console.log(2)) console.log(3) } fun() console.log(4) // 运行结果 //1 //4 //(5s后)2 //3
注意:await 只能运行在一个函数里,且只能运行在async函数里,用在普通函数里会报错。因为await表示这里需要等待,await后面的代码无法立即执行,把await放在async函数里面后,执行到await就会跳出这个函数,继续执行函数外面的代码,等await返回异步操作结果后,再继续执行这个async函数内await后面的代码。另外因为await后面的Promise对象的运行结果有可能是rejected,所有最好把await命令放在try...catch代码块中。
一个sleep函数,暂停程序:
const sleep = (ms) => { return new Promise(resolve => setTimeout(resolve, ms)) }; const demo1 = async() =>{ console.log(1) await sleep(5000).then(() => console.log(2)) console.log(3) } demo1(); // demo1() 或 demo2均可以 const demo2 = async() =>{ console.log(1) await sleep(5000) console.log(2) console.log(3) } demo2(); // 运行结果 // 1 // (5s后)2 // 3