定义:是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
可以按用途理解为:
1.主要用于异步计算
2.可以将异步操作队列化,按照期望的顺序执行,返回执行符合预期的结果。
3.可以在对象之间传递和操作Promise,帮助我们处理队列。
Promise产生的原因
异步操作的常见用法:
// 第一种
document.getElementById("start").addEventlistener("click",start,false);
function start(){
// 响应事件执行相关的操作
}
// 第二种 jqery用on的事件监听
$('start').on('click',start);
回调函数:
//常见的回调函数有Ajax
$.ajax('http://baidu.com',{
sucess: function(res){
// 回调函数
}
})
// 在页面加载完毕后进行加载
$(function(){
// 回调函数
});
在浏览器中JS的异步操作主要以事件为主,回调主要出现在Ajax和File API中。
NodeJs无阻塞高并发大量操作依赖回调函数。在使用不慎时,容易陷入回调地狱。
在异步回调中无法使用try catch,无法使用return 等语句。
异步回调会在一个新的栈中运行,之前的栈是无法拿到之前的栈的信息。在异步回调中无法使用try catch。
总结一下回调带来的四个问题:
- 嵌套层次很深,难以维护;
- 无法正常使用return和throw;
- 无法正常检查堆栈信息;
- 多个回调之间难以联系。
Promise的使用
下面是Promise实例的范例:
new Promise( //创建Promise实例
// 执行器exector
function (resolve, reject){
//一段耗时很长的异步操作
resolve(); // 数据处理完成
reject(); // 数据处理出错
}
)
.then( function A(){
//成功,下一步
},function B(){
//失败,相应处理
});
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise是一个代理对象,它和原先要进行的操作并无关系,它通过引入一个回调,避免更多回调。
Promise有三个状态:
- pending [待定] 初始状态
- fulfilled [实现] 操作成功
- rejected [被否决] 操作失败
Promise状态发生改变,就是触发 .then()里面的响应函数,处理后续操作。
Promise状态一经改变就不是再变。如果Promise函数调用了resolve再调用reject,其还是fulfilled的状态。
Promise实例一经创建,执行器立即执行。
每个then返回一个新的Promise实例。
根据上面的例子可以看出:
- .then()接收两个函数作为参数,分别代表fulfilled和rejected;
- .then()返回一个新的Promise实例,所以它可以链式调用;
- 当前面的Promise状态改变时,.then()会根据其最终状态选择特定的响应执行函数执行;
- 状态响应函数可以返回新的Promise,或者其它值;
- 如果返回新的Promise,那么下一级.then()会在新的Promise状态改变后执行;
- 如果返回其他任何值,则会立即执行下一级.then()。
then()嵌套then()
在then()函数内嵌套then()函数,在一个then()内嵌套一组链式调用效果。
尽量把then展开,变成一个简单的队列。
错误处理
Promise会自动捕获内部异常,并交给reject响应函数处理。
如果在Promise中的执行器发生了错误,Promise的状态会被置为rejected。之后fulfilled函数就不会得到执行,后边通过catch捕获错误的函数就会被执行,执行后返回错误的信息。
then函数接收:fulfilled的响应函数 或者 rejected的响应函数。
错误处理的两种做法:
- reject('错误信息').then(null, message => {})
- throw new Error('错误信息').catch(message => {})
下面展示一下两个方法:
方法一:
new Promise( (resolve, reject) => {
setTimeout( () => {
reject('bye');
},2000);
})
.then( value => {
console.log( value + 'world');
}, value => {
console.log('Error: ',value);
});
运行出的结果为:
方法二:
new Promise( resolve => {
setTimeout( () => {
throw new Error('bye');
},2000);
})
.then( value => {
console.log( value + 'world');
})
.catch( error => {
console.log('Error:'+error.message);
});
运行结果为:
可以看到:第二种方法在更加清晰好读,可以捕获前面的错误。
错误和then连用 (.catch()+.then())
这个是在.catch后接.then()。catch也有Promise实例,如果没有抛出错误的话,它返回的Promise也是fulfilled状态,所以其后的then都会被执行。如果有抛出异常,都会很明显地展示。
注意:强烈建议在所有队列最后都加上catch,以避免漏掉错误处理造成意想不到的问题。
Promise.all()
批量执行:Promise.all([p1,p2,p3,...])用于将多个Promise实例,包装成一个新的Promise实例。
返回的就是一个普通的Promise实例。
接受一个数组作为参数,数组可以是Promise对象,也可以是别的值,只有Promise对象会等待状态改变。当所有的子promise都完成,该Promise才算完成,返回值为全部值的数组。
当有任何一个失败,该Promise失败,返回值是第一个失败的子Promise的结果。
如下用法:
let p1 = new Promise( resolve => {
setTimeout( () => {
resolve('I\'m P1');
}, 1000);
});
let p2 = new Promise( resolve => {
setTimeout( () => {
resolve('I\'m P2');
}, 3000);
});
Promise.all([p1,p2])
.then(value => {
console.log(value);
})
Promise.all()常见的是和map()一起用
实现队列
Promise.then()可实现 then这个函数会返回一个新的promise实例特性,串成一个队列将每次产生新的Promise赋给一个值即可。
实现队列:使用forEach / reduce
但是使用两个方法都会产生一些常见错误:
forEach:没有把then()产生的新的Promise实例赋给Promise,没有生成队列,当新的Promise完成时,后面的then会同步触发。
reduce: Promise实例创建后,会立即运行执行器代码,所以这个无法达到队列的效果。
Promise.resolve()
有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用。返回一个fulfilled的Promise实例或原始的Promise实例。
Promise方法的参数范围四种情况:
- 参数为空,返回状态为fulfilled的Promise实例。
- 参数是一个跟Promise无关的值,同上,不过fulfilled响应函数会得到这个值。
- 参数为Promise实例,则返回该实例,不做任何修改。
- 参数为thenable,立即执行它的 .then()方法。
Promise对象的状态,只能由执行器里面的函数来改变,不能由外界来改变。Promise.resolve()对对象的状态值进行修改。
Promise.reject()
返回一个rejected状态的Promise实例。
这个不认thenable。
Promise.race()
类似Promise.all(),区别在于它有任意一个完成,就算完成。
如下:
let p1 = new Promise( resolve => {
setTimeout( () => {
resolve('I\'m P1');
}, 10000);
});
let p2 = new Promise( resolve => {
setTimeout( () => {
resolve('I\'m P2');
}, 2000);
});
Promise.race([p1,p2])
.then(value => {
console.log(value);
})
常见用法:
将异步操作和定时器放在一起,如果定时器先触发,就认为超时,告知用户。
可见:Promise可将回调函数包装为promise函数。
这样做的优点:1.可读性好 2.返回的结果可以加入任何Promise队列。
async/await
async
函数返回一个 Promise 对象,可以使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
作用:
- 赋予JS以顺序手法编写异步脚本的能力。
- 保留了异步运算的无阻塞特性,还继续使用同步语法
- 还能正常使用return/try/catch。