目录 (づ ̄ 3 ̄)づ=>
[TOC]
## 序
Promise是js、NodeJS中很重要的一个知识点,牵连纵广。
本文会先提出一个promise里的用法,然后再对这个用法进行实现,以此来加深对promise用法的印象并理解之。
## 什么是Promise
Promise翻译成中文是承诺、许诺的意思,这代表着它不是立即被执行,而是在未来的某个时间点才会被一锤定音(不再能更改结果)。一个承诺,在未来可能被实现也可能实现不了,这体现在Promise的设计里就是你可以通过`new Promise`的时候传入一个`executor`执行函数作为参数来决定什么样的情况承诺会被兑现什么样的情况会实现不了。
**Promise不是一个像ajax一样的异步方法,它是帮助我们优化一个异步方法书写形式的方法,以便于异步代码更易书写和维护。**
## 什么时候需要一个Promise
在NodeJS中的读写操作往往是异步的,我们不能立即拿到数据,这意味着那些必须拿到数据后才能做的操作不能放在当前队列中,必须先存起来,当数据拿到后再调用这些操作方法。我们拿到数据后的操作方法有两大类:
- 以拿到的数据为依据去进行下一个异步操作
- 和另外一个异步操作的结果进行拼接后同步输出
对于第一类情况,容易产生一种被称为`回调地狱` 的书写形式
```
$.ajax('url',success(){
...
$.ajax('url',success(){
...
$.ajax('url',success(){
...
})
})
})
```
对于第二类情况,因为都是异步操作,我们不能像常规操作数据拼接一样操作异步方法得来的数据,我们需要实现一个方法来搭桥。
而Promise帮我们完美解决了以上的问题,它就是因此而产生的。
## Promise使用前后对比:只存在一个异步方法时
以下为只使用一次异步操作时的书写对比
```
//使用promise前
readFile('1.txt','utf8',(err,data)=>{
if(err){
//do something
}
if(data){
//do something
}
});
//-----------------------------------------
//使用promise后
let p1 = new Promise((resolve,reject)=>{
readFile('1.txt','utf8',(data,err)=>{
if(err) return reject(err); //读取失败,将err交给promise,并将promise的状态修改为rejected
resolve(data); //读取成功,将data交给promise,并将promise的状态修改为fulfilled
})
});
p1.then((data)=>{ //data为resolve()时传递的参数
//读取成功时执行的回调
},(err)=>{ //err为reject()时传递的参数
//读取失败时执行的回调
})
```
如果只是一次异步操作而不进行嵌套时,似乎原生的异步操作方式更加简洁。。。嗯。。。的确是的,但如果进行多次嵌套的话就不一样了,另外我们还可以在promise的基础之上和生成器结合,使原生异步操作像操作同步方法一样操作异步方法,不过这是后话了。。。总之,promise是基础。。。嗯。。
我们先来实现这段达到代码效果的promise
### Promise实现
**重点是把异步的回调函数先存起来,当promise状态改变时再执行对应的回调函数。**
```
const PENDING = 'pending'; //初始态
const FULFILLED = 'fulfilled'; //顺利执行
const REJECTED = 'rejected'; //失败
function Promise(executor) {
let self = this; //先缓存当前promise实例
self.status = PENDING; //设置状态
self.onResolvedCallBacks = []; //定义存放成功的回调的数组
self.onRejectedCallBacks = []; //定义存放失败的回调数组
self.value = undefined;
//2.1
function resolve(value) { //2.1.1
if (self.status == PENDING) {
self.status == FULFILLED;
self.value = value; //成功后会得到一个值,这个值不能改
self.onResolvedCallBacks.forEach(cb => cb(self.value)); //调用所有成功的回调
}
}
function reject(reason) {
if (self.status == PENDING) { //2.1.2
self.status == REJECTED;
self.reason = reason;
self.onRejectedCallBacks.forEach(cb => cb(self.reason));
}
}
try {
executor(resolve, reject); //因为此函数执行时可能会异常,所以需要捕获,如果出错了,需要用错误对象reject
} catch (e) {
reject(e);
}
}
//onFulfilled 是用来接收promise成功的值或者失败的原因
Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
if (self.status == FULFILLED) { //如果当前promise状态已经是成功态了,onFulfilled直接取值
onFulfilled(self.value);
}
if (self.status == REJECTED) { //如果当前promise状态已经是成功态了,onFulfilled直接取值
onRejected(self.value);
}
if (self.status == PENDING) {
self.onResolvedCallBacks.push(function () {
onFulfilled(self.value);
});
self.onRejectedCallBacks.push(function () {
onRejected(self.value);
});
}
};
module.exports = Promise;
```
## Promise使用前后对比:异步方法嵌套时
读取三个文件,第二个文件的文件名为第一个文件的内容,第三个为第二个文件的内容。
```
//使用promise前
readFile('1.txt','utf8',(err,data)=>{
if(err){
//do something
}
if(data){
readFile(data'utf8',(err,data)=>{
if(err){
//do something
}
if(data){
//do something
//... 再来一层
readFile(data'utf8',(err,data)=>{
if(err){
//do something
}
if(data){
//do something
}
});
}
});
}
});
//-----------------------------------------
//使用promise后
read('1.txt','utf8').then((data)=>{ //data为resolve()时传递的参数
//读取成功时执行的回调
return read(data,'utf8'); //读取第二个文件
).then((data)=>{
return read(data,'utf8'); //读取第三个文件
}).then((data)=>{
console.log(data); //输出第三个文件
}).catch((err)=>{
//如果中间出错了就捕获err做点什么...
})
let read = function(url,encode){
return new Promise((resolve,reject)=>{
readFile(url,encode,(data,err)=>{
if(err) return reject(err); //读取失败,将err交给promise,并将promise的状态修改为rejected
resolve(data); //读取成功,将data交给promise,并将promise的状态修改为fulfilled
})
});
}
```
我们可以发现,这里我们通过类似jQ链式操作一样的方式连续then了三次,**每一次then中的成功回调函数中的参数为这次return的promise的结果(value or reason)**。除此之外若只是return一个普通的值,那么下一次then的成功回调函数中的参数即为这个值。
>[danger] 另外请注意只有在失败回调中抛出一个异常,下一次的then才会走失败回调。而如果只是在失败回调中return,那么下一次则不会走失败回调。
### Promise实现
我们之所以能连续then,是因为**then方法**返回了了promise对象,类似jQ,但这个promise对象是一个新的promise对象。
而这个`新的promise对象的状态`是依据上一个promise的回调函数来决定的,如果上一次的回调函数中的没有抛出异常,那么这个新的promise对象的状态则会为 `fulfilled`。
另外新对象的回调函数中的参数为上一个promise的结果(value/reason)。
```
Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this
, promise2;
if (self.status == FULFILLED) {
return promise2 = new Promise((resolve, reject) => { //调用自己创建一个新的Promise对象
setTimeout(()=>{
try { //2.2.7.2
let x = onFulfilled(self.value); //链式调用时将上一个的promise的结果当做下一次then成功时候的参数传入
resolvePromise(promise2, x, resolve, reject); //2.2.7.1
} catch (e) { //回调函数中抛出了异常
reject(e); //新的promise状态转换为rejected
}
});
});
}
else if (self.status === REJECTED) {
return new Promise((resolve, reject) => {
setTimeout(()=>{
try {
let x = onRejected(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
});
}
else { //pending状态 先将回调函数都储存起来
return promise2 = new Promise((resolve,reject)=>{
self.onFulfilledCallbacks.push(function(){
try{
let x =onFulfilled(self.value);
//如果获取到了返回值x,会走解析promise的过程
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
});
self.onRejectedCallbacks.push(function(){
try{
let x =onRejected(self.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
});
});
}
```
其中`resolvePromise` 的作用为,转换新promise对象的状态并确定其结果的值(value/reason)。
1.resolvePromise(promise2, x, resolve, reject);
2.resolvePromise(promise2, y, resolve, reject); //return promise
3.value.then(resolve,reject); //resolve(promise)
4.onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected == 'function' ? onRejected : reason => {
throw reason
};
5.setTimeout
6.throw err
promise特性