前言
工作中我们会经常使用到Promise,因为在JavaScript的世界中,所有代码都是单线程执行的。 由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行,而Promise是异步编程的一种解决方案。
我们经常使用Promise,那么我们了解它吗?下面通过几个问题来看看我对Promise了解多少:
- 什么是Promise,Promise解决了什么
- 之前用什么方法解决的,还有哪些方法可以实现
- Promise你会用吗
- 你可以自己实现一个promise吗
什么是Promise
什么是Promise,Promise解决了什么
首先我们要清楚什么是Promise:众所周知的,Javascript是一种单线程的语言,所有的代码必须按照所谓的“自上而下”的顺序来执行。本特性带来的问题就是,一些将来的、未知的操作,必须异步实现。 而Promise就是一个比较常见的异步解决方案,它优雅的解决了js的异步问题,并且应用已经极其广泛。
之前用什么方法解决的,还有哪些方法可以实现
同步任务在主线程上排队执行,只有前一个任务执行完毕,才能执行下一个任务。异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。前一个任务是否执行完毕不影响下一个任务的执行。存在异步任务的代码,不能保证能按照顺序执行,如果我们需要代码顺序执行,要怎么写呢?
console.log(1); // 立即执行
setTimeout(()=> {
console.log(2) // 等到1s之后执行
}, 1000);
console.log(3); // 立即执行
当一个任务的执行需要依赖另一个异步任务的结果时,我们一般会将两个任务嵌套起来。
console.log(1); // 立即执行
setTimeout(()=> {
console.log(2); // 等到1s之后执行
console.log(3); // 执行2后执行
}, 1000);
但是如果前端页面交互很复杂的时候,这种嵌套的任务多了,就会形成回调地狱
console.log(1); // 立即执行
setTimeout(()=> {
console.log(2);
setTimeout(()=> {
console.log(3);
setTimeout(()=> {
console.log(4);
setTimeout(()=> {
console.log(5);
setTimeout(()=> {
console.log(6);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
这就是所谓的回调地狱,代码层层嵌套,环环相扣,很明显,逻辑稍微复杂一些,这样的程序就会变得难以维护。
对于这种情况下使用Promise,一定程度上解决了JavaScript的流程操作问题。
Promise你会用吗
Promise的使用
1.1 对Promise的理解
Promise,中文翻译过来就是'承诺',意思是在未来某一个时间点承诺返回数据给你。它是js中的一个原生对象,是一种异步编程的解决方案,可以替换掉传统的回调函数解决方案。
promise本身只是一个容器,真正异步的是它的两个回调resolve()和reject()
promise本质 不是控制 异步代码的执行顺序(无法控制) , 而是控制异步代码结果处理的顺序
-
promise对象有三个状态:pending(进行中),fulfilled(已成功),rejected(已失败)
-
如何改变promise的状态:
- resolve(value): 如果当前是 pending 就会变为 resolved
- reject(error): 如果当前是 pending 就会变为 rejected
- 抛出异常: 如果当前是 pending 就会变为 rejected
注意:一旦从进行状态变成为其他状态就永远不能更改状态了。
1.2 创建Promise实例
一般情况下都会使用new Promise()来创建promise对象,但是也可以使用promise.resolve和promise.reject这两个方法:
- Promise.resolve
Promise.resolve(value)的返回值也是一个promise对象,可以对返回值进行.then调用,代码如下:
Promise.resolve(11).then(function(value){
console.log(value); // 打印出11
});
复制代码
- Promise.reject
Promise.reject 也是new Promise的快捷形式,也创建一个promise对象。代码如下:
Promise.reject(new Error(“我错了!!”));
复制代码
1.3 使用Promise解决地狱回调
1. promise创建时,里面的代码还是异步无序操作
2. promise的原理是,利用then方法将异步操作的结果,按照顺序执行,catch方法用来接收处理失败时相应的数据。
在上一个promise的then方法中,返回下一个promise对象
总结: 不要在创建promise的时候去处理异步操作结果,而应该通过 then() 方法来处理
复制代码
//1.封装一个函数 : 根据文件名生成 文件读取的promise
function getPromise(fileName) {
let p = new Promise((resolve, reject) => {
//读文件
fs.readFile(`./data/${fileName}.txt`, 'utf-8', (err, data) => {
if (err == null) {
//成功
resolve(data);
} else {
//失败
reject(err);
}
});
});
return p;
};
//2.解决需求: 要先读a, 读完a后读b, 读完b后读c.
//开始读取a
getPromise('a').then((data)=>{
console.log(data);
//继续读取b
return getPromise('b');
}).then((data)=>{
console.log(data);
//继续读取c
return getPromise('c');
}).then((data)=>{
console.log(data);
}).catch((err)=>{
//以上三个异步操作,只要有任何一个出错,都会执行err
console.log(err);
});
复制代码
1.4 Promise的实例方法和静态方法
实例方法
then
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中第二个参数可以省略。 then方法返回的是一个新的Promise实例(不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
catch
该方法相当于then方法的第二个参数,指向reject的回调函数。不过catch方法还有一个作用,就是在执行resolve回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入catch方法中。
finally
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
下面是一个例子,服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。
server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
复制代码
finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
静态方法
all
all方法可以完成并发任务, 它接收一个数组,数组的每一项都是一个promise对象,返回一个Promise实例。当数组中所有的promise的状态都达到resolved的时候,all方法的状态就会变成resolved,如果有一个状态变成了rejected,那么all方法的状态就会变成rejected。
race
race方法和all一样,接受的参数是一个每项都是promise的数组,但是与all不同的是,当最先执行完的事件执行完之后,就直接返回该promise对象的值。如果第一个promise对象状态变成resolved,那自身的状态变成了resolved;反之第一个promise变成rejected,那自身状态就会变成rejected。
any
它接收一个数组,数组的每一项都是一个promise对象,该方法会返回一个新的 promise,数组内的任意一个 promise 变成了resolved状态,那么由该方法所返回的 promise 就会变成resolved状态。如果数组内的 promise 状态都是rejected,那么该方法所返回的 promise 就会变成rejected状态,
resolve、reject
用来生成对应状态的Promise实例
Promise.all、Promise.race、Promise.any的区别
all: 成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
race: 哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
any: 返回最快的成功结果,如果全部失败就返回失败结果。
结尾
Promise的问题
Promise虽然摆脱了回调地狱,但是then的链式调⽤也会带来额外的阅读负担,并且Promise传递中间值⾮常麻烦
Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块,因为调试器只能跟踪同步代码的每⼀步。
所以ES2017推出了新的语法async/await 来更好的解决异步问题。