管理异步 的主要机制一直以来都是函数回调,es6增加了一个新的特性来帮助解决只用回调实现异步的严重缺陷,就是promise。
promise到底是什么呢?
我们可以在控制台打印下:
从上面的打印结果可以看出来,Promise是一个构造函数,有all、reject、resolve方法,其中原型上有then、catch等方法。
1、promise概念解释
- promise在回调代码和将要执行这个任务的异步代码之间提供了一种可靠的中间机制来管理回调。
- 也可以把promise看成事件监听者,可以在上面注册某个事件,当任务完成之后会受到通知。
- 当做一个未来值,想象是个独立的容器,无论容器底部值是否确认,都可以用相同的方法应用其值,一旦观察到promise的决议就立即提取此值。
注:promise只能完成或者拒绝一次,之后再次完成或拒绝都会被忽略,因此,一旦promise被决议,值就不会改变。
2、构造和使用promise
let promise = new Promise((resolve, reject) => {// ... })
resolve reject这两个参数都是函数,被称为resolve() reject()
使用方法如下:
- 如果调用reject(),这个promise被拒绝,如果有任何值传给reject(),这个值就被设置为拒绝的原因值。
- 如果调用resolve()并且没有值传入,或者传入非promise值,那么这个promise就完成
- 如果调用resolve()并传入另一个promise,那么这个promise就会采用传入的promise的状态(实现或拒绝)
举例:
通过promise重构回调函数调用的常见方法
ajax (url, cb) => {
// 建立请求,最终会调用cb()
}
ajax ('http://some.url.1', hander(err, contents) => {
if (err) {
// 处理ajax错误
} else {
// 处理contents成功情况
}
})
可以将其转化为
ajax (url) {
return new Promise((resolve, rekect) => {
// 建立请求,最终会调用resolve()或者reject()
})
}
ajax('http://some.url.1').then(fulfilled() = contents => {
// 处理contents成功情况
}, rejected() = reason => {
// 处理ajax出错原因
})
promise有一个then的方法,它接受一个或两个回调函数作为参数,第一个函数会作为promise成功完成后的处理函数,第二个函数会最为promise被显式拒绝后的处理函数,或者是决议过程中出现错误或者异常的情况下的处理函数。
then()和catch()都会自动构造并返回另外一个promise实例,这个实例连接到接受原来的promise不管是完成或拒绝处理函数的返回值。
其实简单的总结下promise的作用,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。
这个时候应该有个疑问:用普通回调函数和用Promise好像也没有多大差别?那为什么要用promise呢?
如果有多层回调怎么弄呢?如果cb也是一个异步操作,并且执行完也需要对应的回调函数,就很麻烦了。而promise的优势在于:可以在then方法中继续写promise对象并返回,然后继续调用then来进行回调函数。
asyncFunc ()
.then(data => {
return asyncFunc1()})
.then(data => {
return asyncFunc2()})
.then(data => {
console.log('data', data)
})
asyncFunc1(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务1执行完成');
resolve('随便什么数据1');
}, 1000);
});
return p;
}
asyncFunc2(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务2执行完成');
resolve('随便什么数据2');
}, 2000);
});
return p;
}
asyncFunc3(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务3执行完成');
resolve('随便什么数据3');
}, 2000);
});
return p;
}
上面大篇幅提到的都是Promise的then方法,下面我们来说说catch方法:
其实它呢和then方法的第二个参数是一样的,都是指定reject的回调
getNumber().then(data => { console.log('resolved')}).catch(reason => { console.log('rejected')})
还有一个作用:在执行resolve的回调时,如果一场,那么并不会卡死js,而是会进入这个catch方法中。
3、Thenable
promise()构造器的真正实例promise,但还有一些类promise对象,称为thenable。
其中任何提供了then()函数的对象或者函数都被认为是thenable,所有可以接受真正promise状态的地方,也可以处理thenable。
thenable这种类promise并不是真正的promise()构造器而是被其他系统创造出来。
例如:
let th = {
then: thener = fulfilled => {
setInterval(fulfilled, 100) // 每100ms调用一次fulfilled,直到永远...
}
}
4、promise API
promise .resolve() ---- 创建了一个决议到传入值的promise
let p1 = Promise.resolve(1);
let p2 = new Promise(pr(resolve) => {
resolve(1)
})
promise.reject() ----- 创建一个立即被拒绝的promise
let p1 = Promise.rejection ('oops')
let p2 = new Promises (pr(resolve, reject) => {
reject('oops')
})
then()和catch()
每个promise实例都有then和catch方法,通过这两个方法可以为这个promise注册完成和拒绝处理函数,promise决议之后,立即会调用这两个处理函数之一,但不会两个都调用。
promise.all() --- 只有传入的所有promise都完成,返回promise才能完成。
我可以用上面定义的asyncFunc1、asyncFunc2、asyncFunc3函数来举例:
Promise
.all(asyncFunc1(), asyncFunc2(), asyncFunc3())
.then(res => {
console.log(res)
})
用Promise.all来执行,all接收一个数组参数,里面的值最终都会返回Promise对象。
这样,三个异步操作并行执行,都执行完后进入then里面。其中,三个操作返回的值都会进入then,all会把所有的结果放在一个数组中传给then。
Promise.race() ------ Promise.race就是赛跑的意思,Promise.race([P1, P2, P3])里面哪个结果获得的快,就返回哪个结果,不管结果本身是成功状态还是失败状态。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('1')
}, 1000)})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('2')
}, 500)})
Promise.race([p1, p2]).then(res => {
console.log(res)}).catch(err => {
console.log(err)
// 会输出2
})
Promise.allSettled
返回一个在所有给定的Promise都fulfilled或者rejected后的promise,并带有一个对象数据,每个对象表示对应的Promise结果,当有多个彼此不依赖的异步任务,成功完成时,或者说总是想知道每个promise的执行结果时,使用:
const promise1 = Promise.reject(new Error('some error'));
const promise2 = 20;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, 'foo');
})
Promise.allSettled([promise1, promise2, promise3]).then(result => {
results.forEach(result => console.log(result);)
}).catch(err => {
console.error(err);
})
** Promise超时问题
例如:已经一个promise的实例,如何给这个实例增加超时限时,超时了就返回超时失败。
首先分析:如果promise实例执行时间过长,我们要返回超时,那可以用另一个带有时间限时的实例与之比较竞争谁率先改变状态,这个可以使用Promise.race解决这个问题。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功了')
}, 2000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('超时失败了')
}, 1000)
})
Promise.race([promise1, promise2]).then((res) => {
console.log(res)
}).catch(e => {
console.log(e)
});
// 超时失败了
5、promise题目
之前看到过一个总结,很详细具体,特此引入: