前置学习依赖&产生背景
我们知道Js这个语言本身是单线程的,但是它有其异步回调的机制,建议围绕如下两个主题自行去学习,这两个主题也是学习Promise的前置依赖:
- 进程 & 线程 区别
- Js的Event Loop与异步
通过对上面两个知识点的学习,Js异步机制我们应该是有概念了,我们知道Js是单线程的,提供了异步回调机制,对于定时器,Ajax,I/O等都交给其他线程处理完成后,再回到主线程的Event Loop(事件循环)。
Js中异步的一种模式是回调,而另一种就是Promise, Promise是es6规范才提出的一个Js对象,提供了几个易用的原型方法和静态方法,从而让异步变的容易为开发者使用,而在es7中,又出现async await新语法糖,进一步优化了Promise使用体验。
一.Promise解决的问题
举个例子, 如果没有Promise:
Settimeout是一个一次性定时器,本质上js交给定时触发器线程处理一个计时任务,定时器线程在规定时间1秒到达时,回调主线程执行传入的回调函数,即打印do someting
setTimeout(function () {
console.log("do someting");
}, 1000);
如果我们想让这个setTimeout执行每秒执行一次,共执行3次怎么办?
setTimeout(function () {
console.log("do someting");
setTimeout(function () {
console.log("do someting");
setTimeout(function () {
console.log("do someting");
}, 1000);
}, 1000);
}, 1000);
问题就出现了,如果要求执行10次,100次,那么这种嵌套将会非常恐怖(被称为回调地狱)。
我们使用Promise后:
function timeOut(t) {
return new Promise(resolve => setTimeout(resolve, t))
}
timeOut(1000).then(res=>{
console.log("do someting");
return timeOut(1000);
}).then(res=> {
console.log("do someting");
return timeOut(1000);
}).then(res=>{
console.log("do someting");
});
层层嵌套的回调被then取代,但then链式调用依旧存在问题,会让链路过长,es6提出了async await 解决了这个问题,简化了语法,让人类更容易理解。
function timeOut(t) {
return new Promise(resolve => setTimeout(resolve, t))
}
async function things(){
await timeOut(1000);
console.log("do someting");
await timeOut(1000);
console.log("do someting");
await timeOut(1000);
console.log("do someting");
};
things();
二.Promise用法
将一个异步回调改成Promise异步:
// XMLHttpRequest的回调形式改成PromiseApi函数
function getData(url) {
return new Promise(function(resolve, reject) {
const req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function () {
if (req.status == 200) {
resolve(req.response);
}
else {
reject(Error(req.statusText));
}
};
req.onerror = function () {
reject(Error("Network Error"));
};
req.send();
});
}
getData('story.json').then(function(res) {
console.log("成功!", res);
}, function(error) {
console.error("失败!", error);
})
Promise状态:
pending:是Promise实例化(new Promise)后的默认状态
fulfilled:表示成功,resolve函数执行后,既转为fulfilled
rejected: 表示执行错误,reject函数执行后,将pending状态转为rejected
Ps:Promise实例状态一旦从pending设置为fulfilled或rejected后,是不可再改变的
Promise常用方法
developer.mozilla.org/zh-CN/docs/…
Promise原型方法(实例方法):
既然是原型方法,都需要实例化(既new Promise(function(resolve, reject){..} )后才能调用,原型方法有:
Promise.prototype.then() Promise.prototype.catch() Promise.prototype.finally()
then的调用形式为:p.then(onFulfilled[, onRejected]);也就是实例化后的 fulfilled和rejected状态都能捕获,但是日常使用中,一般then只关注fulfilled状态的返回即可。
catch是promise实例为rejected状态时的返回。
对于then与catch的使用,如上面例子的getData then中处理fulfilled和rejected,我们让then只处理fulfilled, catch处理rejected状态的返回:
getData('story.json').then(function(res) {
console.log("成功!", res);
}).catch(function(error){
console.error("失败!", error);
});
但是getData.finally则是无论结果状态是 fulfilled 或者是 rejected,都会执行指定的回调函数。
需要注意finally与then的区别,finally不带参数,无法获取fulfilled和rejected的返回值,因此可以在finally写无论fulfilled还是rejected都执行的操作,比如计算这个promise函数执行时间,进一步说可以写超时处理,如果是一个请求后端的操作,则可以是标记完成状态。
let loaded = false
getData('story.json').finally(function() {
// 返回状态为 (resolved 或 rejected)
loaded = true;
});
promise静态方法
Ps:我只列出几个主要的方法,其他的,请查看下文的参考文档。
promise.all: 接收多个promise函数构成的iterable(可迭代对象,如Array,Map,Set),promise.all需要等iterable中的每一个promise都fulfilled才会then,而只要有一个rejected就会整体rejected, 例如:
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values[0]);
console.log(values[1]);
console.log(values[2]);
});
promise.any: 与all一样接受iterable,但如其名,只要有一个promise状态为fulfilled,那么整体就fulfilled,除非所有都rejected,它才会rejected,例子:
const promise1 = Promise.reject(3);
const promise2 = Promise.reject(2);
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.any([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
promise.race: 与all,any类似,它也接收promise对象构成的iterable,他很最公平,只执行最快的一个(这个快是fulfilled都rejected包括的),还是上面的例子:
const promise1 = Promise.reject(3);
const promise2 = Promise.reject(2);
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.race([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
三.部分参考资料
promise规范文档:promisesaplus.com/
使用指南:
上文提到的mdn文档:developer.mozilla.org/zh-CN/docs/…
google爸爸的: web.dev/promises/