一、Promise 的含义
Promise 是异步编程的一种解决方案,他的出现大大改善了异步编程的困境,避免了地狱回调,比传统的解决方案回调函数和事件更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise 对象有以下两个特点:
1、对象的状态不受外界影响。
Promise 对象代表一个异步操作,有三种状态:
pending(进行中)、fulfilled(已成功)rejected(已失败)。
当把一件事情交给 promise 时,它的状态就是Pending,任务完成了状态就变成了fulfilled、没有完成失败了就变成了rejected。
2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise 对象的状态改变,只有两种可能:
pending->fulfilled : Resolved(已完成)pending->rejected:Rejected(已拒绝)
只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。如果改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
Promise缺点:
1、无法取消 Promise ,一旦新建它就会立即执行,无法中途取消。
2、如果不设置回调函数, Promise 内部抛出的错误,不会反应到外部。
3、当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
总结:
Promise 对象是异步编程的一种解决方案,最早由社区提出。Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是pending、fulfilled 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 fulfilled 或者 rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。
状态的改变是通过 resolve() 和 reject() 函数来实现的,可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。
注意: 在构造 Promise 的时候,构造函数内部的代码是立即执行的
二、Promise 的基本用法
1、创建 Promise 对象
Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
一般情况下都会使用new Promise()来创建 promise 对象,但是也可以使用promise.resolve()和promise.reject()这两个方法:
(1)Promise.resolve()
Promise.resolve(value)的返回值也是一个 promise 对象,可以对返回值进行 .then 调用,代码如下:
Promise.resolve(11).then(function(value){
console.log(value); // 打印出11
});
resolve(11) 代码中,会让 promise 对象进入确定(resolve状态),并将参数 11 传递给后面的 then 所指定的 onFulfilled 函数;
(2)Promise.reject()
Promise.reject也是new Promise的快捷形式,也创建一个 promise 对象。代码如下:
Promise.reject(new Error(“我出错了!”));
就是下面的代码 new Promise 的简单形式:
new Promise(function(resolve,reject){
reject(new Error("我错了!"));
});
下面是使用 resolve() 方法和 reject() 方法:
function testPromise(ready) {
return new Promise(function(resolve,reject){
if(ready) {
resolve("hello world");
}else {
reject("No thanks");
}
});
};
// 方法调用
testPromise(true).then(function(msg){
console.log(msg);
},function(error){
console.log(error);
});
上面的代码的含义是给 testPromise 方法传递一个参数,返回一个 promise 对象,如果为 true 的话,那么调用 promise 对象中的 resolve() 方法,并且把其中的参数传递给后面的 then 第一个函数内,因此打印出 “hello world”;如果为 false 的话,会调用 promise 对象中的 reject() 方法,则会进入 then 的第二个函数内,会打印 “No thanks”。
三、Promise 方法
Promise 有五个常用的方法:then()、catch()、all()、race()、finally。下面就来看一下这些方法。
1、then()
当 Promise 执行的内容符合成功条件时,调用resolve函数,失败就调用reject函数。Promise 创建完了,那该如何调用呢?
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then 方法可以接受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为 resolved 时调用,第二个回调函数是 Promise 对象的状态变为 rejected 时调用。其中第二个参数可以省略。 then 方法返回的是一个新的 Promise 实例(不是原来那个Promise实例)。因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法。
当要写有顺序的异步事件时,需要串行时,可以这样写:
let promise = new Promise((resolve,reject)=>{
ajax('first').success(function(res){
resolve(res);
})
})
promise.then(res=>{
return new Promise((resovle,reject)=>{
ajax('second').success(function(res){
resolve(res)
})
})
}).then(res=>{
return new Promise((resovle,reject)=>{
ajax('third').success(function(res){
resolve(res)
})
})
}).then(res=>{
})
那当要写的事件没有顺序或者关系时,还如何写呢?可以使用all方法来解决。
2、catch()
Promise 对象除了有 then 方法,还有一个 catch 方法,该方法相当于then方法的第二个参数,指向reject的回调函数。不过catch方法还有一个作用,就是在执行resolve回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入catch方法中。
p.then((data) => {
console.log('resolved',data);
},(err) => {
console.log('rejected',err);
}
);
p.then((data) => {
console.log('resolved',data);
}).catch((err) => {
console.log('rejected',err);
});
3、all()
all方法可以完成并行任务, 它接收一个数组,数组的每一项都是一个promise对象。当数组中所有的promise的状态都达到resolved的时候,all方法的状态就会变成resolved,如果有一个状态变成了rejected,那么all方法的状态就会变成rejected。
javascript
let promise1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(1);
},2000)
});
let promise2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(2);
},1000)
});
let promise3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(3);
},3000)
});
Promise.all([promise1,promise2,promise3]).then(res=>{
console.log(res);
//结果为:[1,2,3]
})
调用all方法时的结果成功的时候是回调函数的参数也是一个数组,这个数组按顺序保存着每一个promise对象resolve执行时的值。
4、race()
race方法和all一样,接受的参数是一个每项都是promise的数组,但是与all不同的是,当最先执行完的事件执行完之后,就直接返回该promise对象的值。如果第一个promise对象状态变成resolved,那自身的状态变成了resolved;反之第一个promise变成rejected,那自身状态就会变成rejected。
let promise1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject(1);
},2000)
});
let promise2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(2);
},1000)
});
let promise3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(3);
},3000)
});
Promise.race([promise1,promise2,promise3]).then(res=>{
console.log(res);
//结果:2
},rej=>{
console.log(rej)};
)
那么race方法有什么实际作用呢?当要做一件事,超过多长时间就不做了,可以用这个方法来解决:
Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})
5、finally()
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
下面是一个例子,服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。
server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。finally本质上是then方法的特例:
promise
.finally(() => {
// 语句
});
// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
上面代码中,如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。
四、Promise 解决了什么问题
在工作中经常会碰到这样一个需求,比如我使用 ajax 发一个 A 请求后,成功后拿到数据,需要把数据传给 B 请求;那么需要如下编写代码:
let fs = require('fs')
fs.readFile('./a.txt','utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
console.log(data)
})
})
})
上面的代码有如下缺点:
- 后一个请求需要依赖于前一个请求成功后,将数据往下传递,会导致多个 ajax 请求嵌套的情况,代码不够直观。
- 如果前后两个请求不需要传递参数的情况下,那么后一个请求也需要前一个请求成功后再执行下一步操作,这种情况下,那么也需要如上编写代码,导致代码不够直观。
Promise出现之后,代码变成这样:
let fs = require('fs')
function read(url){
return new Promise((resolve,reject)=>{
fs.readFile(url,'utf8',function(error,data){
error && reject(error)
resolve(data)
})
})
}
read('./a.txt').then(data=>{
return read(data)
}).then(data=>{
return read(data)
}).then(data=>{
console.log(data)
})
这样代码看起了就简洁了很多,解决了地狱回调的问题。
五、Promise.all 和 Promise.race 的区别的使用场景
1、Promise.all
Promise.all可以将多个Promise实例包装成一个新的 Promise 实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被 reject 失败状态的值。
Promise.all 中传入的是数组,返回的也是是数组,并且会将进行映射,传入的 promise 对象返回的值是按照顺序在数组中排列的,但是注意的是他们执行的顺序并不是按照顺序的,除非可迭代对象为空。
需要注意,Promise.all 获得的成功结果的数组里面的数据顺序和 Promise.all 接收到的数组顺序是一致的,这样当遇到发送多个请求并根据请求顺序获取和使用数据的场景,就可以使用Promise.all 来解决。
2、Promise.race
顾名思义,Promse.race 就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3]) 里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。当要做一件事,超过多长时间就不做了,可以用这个方法来解决:
Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})