PatrickLh/blog

Javascript学习笔记-Promise

Opened this issue · 0 comments

Javascript中Promise.png

1. Promise简介

Promise是ES6新引入的对象,是新增加的异步处理手段。
在Javascript 中在异步后要进行操作,最常用的手段是回调,例如:

ajax({success: function(data){
  // success logic
}, fail: function(err) {
  // error logic
}})

但是回调存在一些被大家所熟知的问题,类似回调地狱(回调嵌套),而且回调存在的一个很严重的问题,当我们把回调作为方法传递给异步方法以后,我们拿不到任何于这个异步回调相关的句柄,也就是说,我们只能等待回调通过事件循环机制去调用,拿不到和回调相关的任何信息,将回调的控制权转移给了异步方法。
Promise和回调的区别在于Promise在创建以后会返回一个句柄,而我们通过这个句柄,就可以对异步结果进行处理,主动权回到了我们手上。
举个书上简单的例子:我们去麦当劳吃饭(麦当劳绝对没给我赞助。。。),去点单,然后付款,如果采用回调的方式,付款后就什么都不给你,你需要一直等着叫你,叫到你了马上去拿食物,而采用Promise的方式,付款后会给你一个小票,只要食物准备好了,你拿着这个小票可以随时的来获取食物。
也就是说,对于回调来说,回调方法本身就是异步调用结果,而对于Promise来说,对于异步调用结果是类似于事件注册和事件监听(小票)。

2. Promise的创建

2.1 new Promise()

使用new Promise()创建Promise需要传递一个有两个参数的函数

new Promise((resolve, reject) => {
  setTimeout(_=>{resolve(1)}, 1000);
})

这里有几点需要注意的内容:

  1. Promise中的函数会立即执行
  2. resolvereject将产生决议值,只会决议一次,产生一个决议值,且决议值在决议后不会发生变化
  3. resolve会尝试展开参数的值,展开按照以下规则:
    3.1 如果参数是Promise类型或者thenable(下面会说明)类型,那么会尝试进行展开该参数,直到拿到最后的决议值
    3.2 如果参数是非上述情况,则直接将该值作为决议值
  4. reject会将参数值作为决议值直接返回

分别做一下说明:
Promise中的函数会立即执行,也就是说,Promise函数本身不是异步的,但是其中可以包含异步过程

console.log(1);
new Promise((resolve, reject)=> {
  console.log(2);
  setTimeout(_=>{console.log(4);},1000)
})
console.log(3);
// 输出结果是 1,2,3,4

决议值,是异步方法后返回的结果,一个Promise只能产生一个决议值,而且只能决议一次,一旦决议就会永远保持这个状态,成为不变值。也就是通过resolve或者reject方法处理后的结果,只会将第一次的结果作为决议值

// 只能决议一次
var p1 = new Promise((resolve, reject) => {
  resolve(1);
  resolve(2);
})
p1.then(fulfill => {
  console.log(fulfill); // 1
});
// 决议后值不变, 可以多次调用
p1.then(fulfill => {
  console.log(fulfill); // 1
});
p1.then(fulfill => {
  console.log(fulfill); // 1
})

thenable类型是指含有then方法的对象,包括原型链上还有then方法的对象(then方法可以有两个参数,分别对应了resolvereject

var obj = {
  then(cb, ecb) {
     cb(1);
  }
}

resolve展开可以通过下面的例子理解

var obj = {
  then(cb, ecb) {
     cb(1);
  }
}
var p1 = new Promise((resolve, reject) => {
  resolve(1)
})
new Promise((resolve, reject) => {
  resolve(obj); // 或者 resolve(p1)
}).then(fulfill => {
  console.log(fulfill); // 1
})

reject不进行展开可以通过下面的例子理解

var obj = {
  then(cb, ecb) {
     cb(1);
  }
}
var p1 = new Promise((resolve, reject) => {
  resolve(1)
})
new Promise((resolve, reject) => {
  reject(obj); // 或者 reject(p1)
}).then(null, reject => {
  console.log(reject === obj); // true,或者console.log(reject === p1)
})

2.2 Promise.resolve()

Promise.resolve()会接收一个对象,并返回一个决议的Promise,根据接收对象不同,返回的Promise的决议值会有不同操作

  1. 如果接受对象是Promise类型,那么将直接返回该对象
  2. 如果接受对象是thenable类型,那么会尝试展开,将最后的决议值作为返回Promise的决议值
  3. 非以上两种情况,将该值作为返回Promise的决议值

这里有两个地方需要注意

  • Promise类型作为参数和之前resolve的处理方式不同
  • 这里的Promise.resolve产生的Promise决议值不一定就是成功的,也有可能是失败的

用例子是最好说明的:

var obj = {
  then(cb, ecb) {
     cb(1);
  }
}
var p1 = new Promise((resolve, reject) => {
  resolve(1)
})
// Promise对象直接返回
console.log(p1 === Promise.resolve(p1)); // true
// 展开thenable
Promise.resolve(obj).then(fulfill => {
  console.log(fulfill); // 1
})
// 直接作为决议值
Promise.resolve(obj).then(fulfill => {
  console.log(fulfill); // 1
})
// 返回的决议值为失败
Promise.resolve(new Promise((resolve, reject)=>{
  reject(1)
})).then(fulfill => {
  // 不会运行到这里
  console.log('fulfill');
  console.log(fulfill);
}, reject => {
  // 输出结果
  console.log('reject');
  console.log(reject);
})

2.3 Promise.reject()

Promise.reject()也是接收一个参数,会产生一个决议值一定为rejectPromise,且和reject一样,将参数作为返回Promise的决议值

var obj = {
  then(cb, ecb) {
     cb(1);
  }
}
var p1 = new Promise((resolve, reject) => {
  resolve(1)
})
console.log(p1 === Promise.reject(p1)); // false,因为相当于返回Promise(p1)
// 展开thenable
Promise.reject(obj).then(fulfill => {
  // 不会运行到这里
}, reject => {
  console.log(obj === reject); // true
})

3. Promise的使用

在获取到Promise以后,调用then方法可以对决议值进行操作,then方法调用的时候包含两个参数,分别接收成功和拒绝时候的决议值,和我们经常将回调拆分为成功回调合失败回调的做法类似。

var p1 = new Promise((resolve ,reject) => {
  resolve(1);
});
p1.then(fulfill => {
  // 如果决议成功将调用
  console.log('fulfill');
  console.log(fulfill); // 1
}, reject => {
  // 如果决议失败将调用
  console.log('fulfill');
  console.log(reject);
})

4. Promise的模式

Promise模式是为了处理多个异步事件之间的关系

4.1 Promise.all()

Promise.all()接受一个数组作为参数,他会等待所有的Promise都完成以后,返回一个决议值,这个决议值是根据传递参数数组顺序,将每一个Promise的决议值放倒对应的位置,如果数组中任何一个Promise拒绝,则会将该拒绝值作为结果,如果是空数组,则会立即进行决议

var obj = {
  then(cb, ecb) {
    setTimeout(_=> cb(2), 100);
  }
}
var p1 = new Promise((resolve, reject) => {
  setTimeout(_=> resolve(3), 100);
})
Promise.all([1, obj, p1]).then(fulfill => {
 // 可以利用解构来获取到对应的值 var [v1,v2,v3] = fulfill
  console.log(fulfill.length === 3); // true
  console.log(fulfill[0] === 1); // true
  console.log(fulfill[1] === 2); // true
  console.log(fulfill[2] === 3); // true
})
// 有任何拒绝,则拒绝值作为结果
Promise.all([1, obj, p1, Promise.reject(4)]).then(fulfill => {
}, reject => {
  console.log(reject === 4); // true
})
// 空数组立即决议
Promise.all([]).then(fulfill => {
  console.log('fulfill');
})

4.2 Promise.race()

Promise.race()Promise.all()类似,同样接受一个数组作为参数,但是区别在于Promise.race()只会返回最快进行决议的结果作为结果值,且如果为空数组则永远不会进行决议(相当于永远在等待最快的决议的结果,但是空数组一直没有决议,所以会一直等待)

var p1 = new Promise((resolve, reject) => {
  setTimeout(_=> resolve(1), 100);
})
var p2 = new Promise((resolve, reject) => {
  setTimeout(_=> resolve(2), 150);
})
var p3 = new Promise((resolve, reject) => {
  setTimeout(_=> reject(3), 50);
})
Promise.race([p1, p2]).then(fulfill => {
  console.log('fulfill');
  console.log(fulfill === 1); // true, 因为p1会比p2先进行决议
})
Promise.race([p1, p3]).then(fulfill => {
}, reject => {
  console.log('reject');
  console.log(reject === 3); // true, 因为p3会比p1先进行决议,且决议拒绝
})
// 空数组永远不会决议
Promise.race([]).then(fulfill => {
  console.log('fulfill'); // 永远不会执行
}, reject => {
  console.log('reject'); // 永远不会执行
})

5. Promise的特点

5.1 Promise链

Promise在执行then方法以后,会返回一个Promise对象,这个新的Promise的决议值是根据上一个then方法中的return值来确定的,于是形成了一个Promise的链路

var p1 = new Promise((resolve, reject) => {
  setTimeout(_=> resolve(1), 100);
});
var p2 = p1.then(fulfill => {
  console.log(fulfill); // 1
  return 2; // 返回的值会作为链路上新的Promise的决议值
});
// 其实p2相当于
var p2 = new Promise((resolve, reject) => {
  resolve(2);
});

5.2 Promise异常处理

Promise和回调一样,因为决议过程是异步,所以没办法使用try catch来捕获异常,Promise链路上如果产生异常,那么会认为Promise决议被拒绝,该异常会作为拒绝的决议值,放倒链路上下一个Promise中。

var err = new Error(1);
var p1 = new Promise((resolve, reject) => {
  throw err;
  setTimeout(_=> resolve(1), 100);
});
var p2 = p1.then(fulfill => {
    console.log('fulfill'); // 不会执行到这里 
}, reject => {
  console.log(reject === err); // true
});

也就是说p1产生的异常,自身并不能进行处理,必须要使用链路上下一个Promise也就是p2中处理,如果不进行处理,那么该错误就会被丢掉了
同时,对于异常处理可以使用catch方法,接收一个参数,相当于then(null, reject)

var err = new Error(1);
var p1 = new Promise((resolve, reject) => {
  throw err;
  setTimeout(_=> resolve(1), 100);
});
var p2 = p1.catch(reject => {
  console.log(reject === err); // true
});

6. 总结

Promise比起回调来说,可以减少回调地狱带来的代码风格问题,也将控制权重新回到了我们手中。

7. 参考

《你不知道的Javascript(中篇)》
MDN-Promise