一、promise是什么?
promise用于异步计算,将异步操作队列化,按照预期的顺序进行,返回符合预期的结果。
1.1异步操作
JavaScript的执行环境是单线程的,所谓单线程是指JS中负责执行JS代码的线程只有一个,JavaScript为检查表单而生,存在的首要目标就是操作DOM,所以JS中很多操作是异步的。
同步:一次只能执行一个任务,函数调用后需要等到函数执行结束,返回执行结果才能进行下一个任务。
异步:与同步相反,可以一起执行多个任务。
1.2常见异步操作
(1)时间侦听与响应
document.getElementById('start').addEventListener('click',start,false);
function start(){
//响应事件,进行相应的操作
}
//jQuery 用‘.on()’也是事件侦听
$('start').on('click',start);
(2)回调函数
回调函数是一个可执行的代码段,以参数的形式传递给其他代码。
//比较常见的有AJAX
$.ajax('http://baidu.com',{
success:function(res){
//这里就是回调函数了
}
});
//或者在页面加载完毕后回调
$(function(){
//这里也是回调函数
})
浏览器中的JavaScript,异步以操作事件为主,回调主要出现在AJAX和File API,此时问题还不算严重,但是node.js出现之后,对异步的依赖加剧了,node.js主要实现的是无阻赛和高并发。所以异步操作是其保障。
二、Promise简介
其基本结构有:
new Promise(
//执行器 executor
function(resolve,reject){
//一段耗时很长的异步操作
resolve();//数据处理完成
reject();//数据处理出错
}
).then(function A(){
//成功,下一步
},function B(){
//失败,做相应处理
})
先初始化Promise实例,实例中传入一个参数,参数是一个函数,称其为执行器(executor),执行器中有两个参数:resolve和reject。可以将需要操作的事件放在执行器中进行执行。当操作执行完成之后,如果数据处理完成,调用resolve()方法,如果数据处理出错,则调用reject()方法。调用之后,将会改变当前Promise实例的状态,状态改变之后就会调用then()函数中对应的处理函数,如果调用的是resolve(),则执行A函数,否则如果调用reject()函数,Promise状态变为rejected,会调用B()函数。
Promise是一个代理对象,他和原先要进行的操作并无关系,只引入一个回调,避免更多的回调。
执行图为:
2.1Promise的3个状态
- pending:初始状态
- fulfilled:操作成功
- rejected:操作失败
当Promise的状态改变之后,就会立刻触发.then()中的相应函数处理后续。而且状态改变只有两种情况: - 初始状态pending转为fulfilled:表示数据处理成功
- 初始状态pending转为rejected:表示数据处理失败
2.2Promise定时例子
console.log('here we go');
new Promise(resolve=> {
setTimeout(()=>{
resolve('hello');
},2000);
}).then(value=> {
console.log(value+'world');
});
输出结果为:
here we go
helloworld
由此可以了解Promise运行的一般步骤。
特殊情况:假如在.then()的函数里面不返回新的Promise(),会怎样?
console.log('here we go');
new Promise(resolve=> {
setTimeout(()=>{
resolve('Hello');
},2000);
}).then(value=> {
console.log(value);
console.log('everyOne');
(function(){
return new Promise(resolve=> {
setTimeout(()=>{
console.log('Mr.Laurence');
resolve();
},2000);
});
}());
return false;
}).then(value =>{
console.log(value +' World');
})
输出结果:
here we go
Hello
everyOne
false World
Mr.Laurence
[Done] exited with code=0 in 4.273 seconds
由结果可以看很出,当.then()函数里面不返回新的Promise时,会直接执行下一个.then()函数,并且也是直到所有代码执行结束之后,进程才结束。
2.3.then()函数
- .then()接受两个函数作为参数,分别是fulfilled状态下的响应函数和rejected状态下的响应函数。
- .then()返回一个新的Promise实例,所以他可以链式调用
- 当前面的Promise状态改变时,.then()根据其最终状态,选择特定的状态响应函数执行。
要注意:
- 状态响应函数可以返回新的Promise,或其他值
- 如果返回新的Promise,那么下一级.then()会在新Promise状态改变之后执行
- 如果返回其他值,则会立即执行下一级.then()。
2.4.then()函数的链式结构
console.log('here we go');
new Promise(resolve=> {
console.log('step 1');
setTimeout(()=>{
resolve();
},1000);
}).then(value =>{
return new Promise(resolve=> {
console.log('step 1-1');
setTimeout(()=> {
resolve(110);
},1000);
})
}).then(value => {
console.log('step 1-2');
return value;
}).then(value => {
console.log('step 1-3');
return value;
}).then(value=> {
console.log(value);
console.log('step 2');
})
输出结果:
here we go
step 1
step 1-1
step 1-2
step 1-3
110
step 2
2.5Promise错误处理
Promise 会自动捕获内部异常,并交给rejected响应函数处理。
错误处理的两种做法:
- reject(‘错误信息’).then(null,message => {})
- throw new Error(‘错误信息’).catch(message=> {})
推荐使用第二种,更加清晰好读,并且可以捕获前面的错误。
console.log('here we go');
new Promise(rsolve=> {
setTimeout(() => {
throw new Error('bye');
},1000);
}).then(value=> {
console.log(value);
}).catch(error => {
console.log('error:'+error.message);
})
输出结果:
here we go
d:\biancheng\java\javascript\JavaScript\timeOut.js:4
throw new Error('bye');
^
Error: bye
at Timeout.setTimeout [as _onTimeout] (d:\biancheng\java\javascript\JavaScript\timeOut.js:4:15)
at ontimeout (timers.js:498:11)
at tryOnTimeout (timers.js:323:5)
at Timer.listOnTimeout (timers.js:290:5)
如果调用rejected回调函数情况:
new Promise(rsolve=> {
setTimeout(() => {
throw new Error('bye');
},1000);
}).then(value=> {
console.log(value);
},value => {
console.log('Error:'+value);
})
输出结果:
d:\biancheng\java\javascript\JavaScript\timeOut.js:4
throw new Error('bye');
^
Error: bye
at Timeout.setTimeout [as _onTimeout] (d:\biancheng\java\javascript\JavaScript\timeOut.js:4:15)
at ontimeout (timers.js:498:11)
at tryOnTimeout (timers.js:323:5)
at Timer.listOnTimeout (timers.js:290:5)
catch也将返回一个Promise实例,所以存在catch后面得.then()也将会依次执行。此外推荐在所有队列最后都加上.catch(),以避免漏掉错误处理造成意想不到的问题。
三、Promise进阶
3.1Promise.all()
批量执行一堆Promise的意思。即Promise.all([p1,p2,p3,…])用于将多个Promise实例,包装成一个新的Promise实例。返回一个新的Promise,接收一个数组作为参数,数组可以是Promise对象,也可以是别的值,只有Promise对象会等待状态的改变。
当所有子Promise都完成,该Promise完成,返回值是全部值的数组,
有任何一个失败,该Promise失败,返回值是第一个失败的子Promise的结果。
console.log('here we go');
Promise.all([1,2,3])
.then(all =>{
console.log('1: '+ all);
return Promise.all([function(){
console.log('ooxx');
},'xxoo',false]);
}).then(all => {
console.log('2: '+all);
let p1= new Promise(resolve => {
setTimeout(()=>{
resolve('I\'m p1');
},1500);
});
let p2= new Promise(resolve=> {
setTimeout(() => {
resolve('I\'m p2');
},1450);
});
return Promise.all([p1,p2]);
}).then(all => {
console.log('3: '+all);
let p1=new Promise(resolve => {
setTimeout(() => {
resolve('I\'m p1');
},1450);
});
let p2=new Promise((resolve ,reject)=> {
setTimeout(() => {
reject('I\'m p2');
},1500);
});
let p3=new Promise((resolve,reject) => {
setTimeout(() => {
reject('I\'m p3');
},2000);
});
return Promise.all([p1,p2,p3]);
}).then(all => {
console.log('all:' +all);
}).catch(err => {
console.log('catch:'+ err);
})
输出结果:
here we go
1: 1,2,3
2: function (){
console.log('ooxx');
},xxoo,false
3: I'm p1,I'm p2
catch:I'm p2
其最常见的用法是与map结合使用。
3.2Promise.resolve()
返回一个fulfilled的Promise实例或原始Promise实例。
- 参数为空,返回一个状态为fulfilled的Promise实例,
- 参数是一个跟Promise无关的值,同上,不过fulfilled响应函数会得到这个参数。
- 参数为Promise实例,则返回该实例,不做任何修改。
- 参数是thenable,立刻执行他的.then()函数。
console.log('start');
Promise.resolve() //参数为空,返回一个状态为fulfilled的Promise实例
.then((value) => {
console.log('step 1 ',value);
return Promise.resolve('Hello');//参数是一个跟Promise无关的值,同上,
}).then(value => {
console.log(value,' World');
return Promise.resolve(new Promise(resolve=> {
setTimeout(()=> {
resolve('Good');
},2000);
}));//参数是一个Promise实例,则返回该实例,不做任何修改
}).then(value => {
console.log(value, ' evening');
return Promise.resolve({
then(){
console.log(' everyone');
}//参数是thenable则立即执行.then()函数。
});
})
输出结果:
start
step 1 undefined
Hello World
Good evening
everyone
3.3Promise.reject()
Promise.reject()返回一个rejected状态的Promise实例,与Promise.resolve()不同之处就是Promise.reject()不认thenable
let promise = Promise.reject('something wrong');
promise.then(()=> {
console.log('it is OK');
}).catch(()=> {
console.log('no, it is not Ok');
return Promise.reject({
then(){
console.log('it will be Ok');
},
catch(){
console.log('not yet');
}
});
});
结果将输出:no, it is not Ok
3.4Promise.race()
Promise.race()类似于Promise.all(),区别在于他有任意一个完成就算完成。不需要等待所有实例都完成。
常见用法是:把异步操作和定时器放在一起,如果定时器先触发,就认为超时,告知用户。
Promise常见的用法就是把回调包装成Promise实例,他有两个显而易见的好处:
- 可读性更好
- 返回的结果可以加入任何Promise队列。
结语
本篇文章仅仅是我作为一个刚开始找工作时,刷面经刷到Promise的原理经常会在面试中被问到,所以对其进行了学习总结,例子都来自于慕课网一个老师的视频教程课中,比较简单易懂。但是只适合入门,如果要深入掌握还需要进一步学习。(Meathill的《Promise入门》)