首先先理解以下几个概念的概念:
同步:一次只能执行一次任务,这个任务执行完之后才能执行下一个,它会阻塞其他任务。
异步:可以一起执行多个任务。
回调地狱:回调函数的层层嵌套,导致代码层次过多,不好理解和维护。
一、Promise的含义
先简单了解以下Promise的含义和特性:
-
Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更加合理和强大;
-
所谓的Promise,简单来说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果;
-
从语法上来说,Promise是一个对象,从它可以获取异步操作的消息;
-
Promise对象代表的是一个异步操作,有三种状态: pending(等待状态)、 fulfilled(成功状态)、 rejected(失败状态)
Promise对象的特点:
1. 对象的状态不收外界的影响;
只有异步操作的结果才能决定当前是哪一种状态,任何其他操作都无法改变这个状态。
2. 一旦状态改变就不会再变;
Promise对象状态改变只有两种可能:
- 从pending变为fulfilled
- 从pengding变为rejected
只要这两种状态发现了,状态就凝固了,不会再变了,会一直保持这个结果,这时称为resolved(已定型)
3. 每个Promise实例都有一个then方法,一个Promise可以.then多个;
4. 每次执行Promise的时候,都会返还一个新的Promise实例;
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,解决了异步层层嵌套的函数(简称回调地狱)问题。
说到 Promise,我们首先想到的最核心的功能就是异步链式调用。
Promise对象的缺点:
-
无法取消Promise。一旦新建它就会立即执行,无法中途取消;
-
如果不设置回调函数,Promise内部抛出错误,不会反应到外部;
-
当处于Pending(进行中)状态时,无法得知目前进展到哪一个节点(刚开始还是即将完成);
使用Promise对象的优点:
- 可以解决异步嵌套问题(回调地狱);
- 可以解决多个异步并发问题;
二、基本用法
注意,为了行文方便,本章后面的resolved统一只指fulfilled状态,不包含rejected状态。
1、创造一个Promise实例
ES6规定,Promise对象时一个构造函数,使用new来生成Promise实例。
let promise = new Promise(() => {//executor执行器,特点是立即执行
})
这个Promise实例是一个类,允许接受一个函数作为参数,这个函数叫做executor执行器,特点是立即执行。如下会立即打印:
let promise = new Promise(() => {//executor执行器,作用立即执行
console.log("立即执行");//Promise有三种状态,此处默认Pending状态
})
console.log("222");
/*控制台打印*/
//立即执行
//222
2、实例接受两个参数resolve,rejected,使用then
函数的可以接受两个参数resolve(代表成功状态)和rejected(代表失败状态),而且每个Promise实例都有一个
then方法。(注意:resolve和rejected必须配合then一起使用,不然会打印空值)
then分别放置了两个函数:
- onfulfilled 实例成功要执行的逻辑
- onrejected 实例失败要执行的逻辑
let promise = new Promise((resolve,rejected) => {
resolve('成功');//如果这里调用的是resolve,那边then就会走成功的逻辑;
}).then(data => {//成功
console.log(data);
},err => {//失败
console.log(err);
})
/*控制台打印*/
//成功
let promise = new Promise((resolve,rejected) => {
rejected('失败');//如果这里调用的是rejected,那边then就会走失败的逻辑;
}).then(data => {//成功
console.log(data);
},err => {//失败
console.log(err);
})
// 失败
//上边的代码也可以这样写:
let promise = new Promise((resolve, rejected) => {
resolve('成功');
})
promise.then(data => {
console.log(data);
}, err => {
console.log(err);
})
因为Promise实例的状态一旦状态改变就不会再变,所以调用resolve之后再调用rejected,后一步是不会生效的。反之亦然。
let promise = new Promise((resolve,rejected) => {
resolve('成功');
rejected('失败');//这一步不会执行
}).then(data => {//成功
console.log(data);
},err => {//失败
console.log(err);
})
//成功
如果Promise实例内部报错,就会变成失败状态,不会执行后边的方法:
let promise = new Promise((resolve, rejected) => {//executor执行器,作用立即执行
throw new Error('内部抛出错误');//内部抛出错误,就会变成失败状态,then就会走失败的逻辑
resolve('成功');//这一步依然不会执行
}).then(data => {//成功
console.log(data);
}, err => {//失败
console.log(err);
})
//Error: 内部抛出错误
// ...
三、Promise实例的原理
1、手写一个基础版的Promise
新建一个promise.js,并新建一个Promise类,并导出:
//promise.js:
class Promise {
};
module.exports = Promise;//导出
将前面的代码引入promise.js:
//callBack.js:
let Promise = require('./promise');
//引入自定义的Promise,相当于下面用的Promise就是自定义的Promise
let promise = new Promise((resolve, rejected) => {
resolve('成功');
}).then(data => {
console.log(data);
}, err => {
console.log(err);
})
根据callBack.js的Promise实例,自定义一个基础版的Promise,不考虑其他异常情况:
(1)创建公共的then方法
Promise实例有三种状态,不管是哪一种状态都会执行的then方法,所以then方法属于公共的属性。
//promise.js:
class Promise {
then(){
}
};
module.exports = Promise;//导出
每一个Promise实例都有自己的三个状态,所以将三种状态放到对应的构造函数(constructor)中。
在构造函数中可以拿到Promise实例的状态,因为这个三个状态会经常用到,所以我们可以把它们存成一个常量。
//promise.js:
const PENDING = "PENDING";//等待状态
const RESOLVED = "RESOLVED";//成功状态
const REJECTED = "REJECTED";//失败状态
class Promise {
constructor(){//构造函数
this.status = PENDING;//Promise实例的默认pending状态
}
then(){
}
};
module.exports = Promise;//导出
我们在callBacks.js的new Promise实例时,传了一个函数(也就是一个执行器),并且这个函数是立即执行的,所以我们要给构造函数也传递一个executor执行器:
//promise.js:
const PENDING = "PENDING";//等待状态
const RESOLVED = "RESOLVED";//成功状态
const REJECTED = "REJECTED";//失败状态
class Promise {
constructor(executor){//构造函数
this.status = PENDING;//Promise实例的默认pending状态
executor();//默认执行器会立即执行
}
then(){
}
};
module.exports = Promise;//导出
callBacks.js的new Promise实例时,executor执行的时候传递了两个函数,并且属于当前的Promise实例,不需要再then中拿到,所以我们可以在constructor中声明两个函数:成功函数和失败函数。并且将这两个函数传递给executor执行器:
//promise.js:
const PENDING = "PENDING";//等待状态
const RESOLVED = "RESOLVED";//成功状态
const REJECTED = "REJECTED";//失败状态
class Promise {
constructor(executor){//构造函数
this.status = PENDING;//Promise实例的默认pending状态
//成功函数
let resolve = () => {
}
//失败函数
let reject = () => {
}
executor(resolve,reject);//默认执行器会立即执行
}
then(){
}
};
module.exports = Promise;//导出
成功函数和失败的函数都接受一个值,value和reason,因为在then也需要用到这两个值,所以我们需要定义一个变量。我们在执行完成功和失败函数之后,需要更新Promise的状态:
//promise.js:
const PENDING = "PENDING";//等待状态
const RESOLVED = "RESOLVED";//成功状态
const REJECTED = "REJECTED";//失败状态
class Promise {
constructor(executor){//构造函数
this.status = PENDING;//Promise实例的默认pending状态
this.value = undefined;//成功的值
this.reason = undefined;//失败的原因
//成功函数
let resolve = (value) => {
this.value = value;
this.status = RESOLVED;//Promise实例状态更新为成功状态:resolved状态
}
//失败函数
let reject = (reason) => {
this.reason = reason;
this.status = REJECTED;//Promise实例状态更新为失败状态:rejected状态
}
executor(resolve,reject);//默认执行器会立即执行
}
then(){
}
};
module.exports = Promise;//导出
因为Promise实例的状态一旦改变了就不能再变了,也就是说我们调用了resolve函数就不能再调用reject函数了,所以我们必须添加添加状态判断,只要状态时等待状态pending时,才执行函数:
//promise.js:
const PENDING = "PENDING";//等待状态
const RESOLVED = "RESOLVED";//成功状态
const REJECTED = "REJECTED";//失败状态
class Promise {
constructor(executor){//构造函数
this.status = PENDING;//Promise实例的默认pending状态
this.value = undefined;//成功的值
this.reason = undefined;//失败的原因
//成功函数
let resolve = (value) => {
if(this.status === PENDING){//// 屏蔽调用,调了成功函数之后不能调失败函数
this.value = value;
this.status = RESOLVED;//调用成功之后,Promise状态变成resolved状态
}
}
//失败函数
let reject = (reason) => {
if(this.status === PENDING){
this.reason = reason;
this.status = REJECTED;////调用失败之后,Promise状态变成rejected状态
}
}
executor(resolve,reject);//默认执行器会立即执行
}
then(){
}
};
module.exports = Promise;//导出
因为执行器执行的时候内部可能会报错,会抛出异常,这个时候添加try...catch进行逻辑处理,如果内部抛出异常的话相当于调用了reject失败函数:
接下来开始调用then( )方法,then可以放置了两个函数:
- onfulfilled 实例成功要执行的逻辑
- onrejected 实例失败要执行的逻辑
那么什么时候调用哪一个方法呢?这要根据Promise实例的状态来判断了:
Promise实例还有订阅发布功能:
未完待续,后续更新~~
promise.js完整代码:
//promise.js:
const PENDING = "PENDING";//等待状态
const RESOLVED = "RESOLVED";//成功状态
const REJECTED = "REJECTED";//失败状态
class Promise {
constructor(executor){//构造函数
this.status = PENDING;//Promise实例的默认pending状态
this.value = undefined;//成功的值
this.reason = undefined;//失败的原因
this.onResolveCallBacks = [];//成功的回调的数组
this.onRejectCallBacks = [];//失败的回调的数组
//成功函数
let resolve = (value) => {
if(this.status === PENDING){//// 屏蔽调用,调了成功函数之后不能调失败函数
this.value = value;
this.status = RESOLVED;//调用成功之后,Promise状态变成resolved状态
this.onResolveCallBacks.forEach(fn => fn())//发布
}
}
//失败函数
let reject = (reason) => {
if(this.status === PENDING){
this.reason = reason;
this.status = REJECTED;////调用失败之后,Promise状态变成rejected状态
this.onRejectCallBacks.forEach(fn => fn())//发布
}
}
//执行器执行时,可能内部报错:
try{
executor(resolve,reject);//默认执行器会立即执行,接受resolve,reject作为参数
}catch(error){
reject(error);//如果执行器执行时发生错误,等价于调用了失败方法
}
}
then(onfulfilled,onrejected){/// then 目前有两个参数:onfulfilled,onrejected
//同步情况:成功之后执行逻辑
if(this.status === RESOLVED){
onfulfilled(this.value);
}
//同步情况:失败之后执行逻辑
if(this.status === PENDING){
onrejected(this.reason);
}
// 如果是异步就先订阅号
if (this.status === PEDING) {
this.onResolveCallBacks.push(() => {
// todo
onfulfilled(this.value)
})
this.onRejectCallBacks.push(() => {
// todo
onrejected(this.value)
})
}
}
};
module.exports = Promise;//导出
五、常见的Promise面试题
1、实现函数sleep,先输出A,1秒之后输出B,有什么方案吗?
(1)通过Promise实现:
console.log('A');
function sleep(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, time);
})
};
sleep(1000).then(() => {
console.log('B');
});
//先输出A,延迟1秒之后输出B
//或
console.log('A');
const sleep = ((time)=>{
return new Promise((resolve)=>{
setTimeout(()=>{
resolve();
},time)
})
})
sleep(1000).then(()=>{
console.log('B');
})
(2)通过async/awiat进行实现:
const sleep = ((time)=>{
return new Promise((resolve)=>{
setTimeout(()=>{
resolve();
},time)
})
})
async function sleepAsync(){
await sleep(1000);
console.log('B');
}
(3)从Generator配合yield进行实现
console.log("A");
const sleep = ((time)=>{
return new Promise((resolive)=>{
setTimeout(()=>{
resolve();
},time)
})
})
function* sleepGenerator(time){
yeild sleep(time);
}
sleepGenerator(1000).next().value.then(()=>{
console.log("B");
})
相似的面试题:
经常有业务需求,要等几秒才进行下一步操作,也是使用以上的思路
2、红灯三秒亮一次,绿灯一秒亮一次,黄灯2秒亮一次;如何让三个灯不断交替重复亮灯?(用Promse实现+递归)
三个亮灯函数已经存在:
思路:
红灯三秒亮一次,绿灯一秒亮一次,黄灯2秒亮一次,意思就是3秒,执行一次 red 函数,2秒执行一次 green 函数,1秒执行一次 yellow 函数,不断交替重复亮灯,意思就是按照这个顺序一直执行这3个函数,这步可以就利用递归来实现。
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
const sleep = ((time,fn)=>{
return new Promise((resolve)=>{
setTimeout(()=>{
fn();//哪个灯亮
resolve();
},time)
})
})
let step = (()=>{
Promise.resolve().then(()=>{
return sleep(3000,red);
}).then(()=>{
return sleep(2000,green);
}).then(()=>{
return sleep(1000,yellow);
}).then(()=>{
step();
})
})
3、输出下面的执行结果:
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
//输入1
Promise.resolve 方法的参数如果是一个原始值,或者是一个不具有 then 方法的对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为resolved,Promise.resolve 方法的参数,会同时传给回调函数。
then 方法接受的参数是函数,而如果传递的并非是一个函数,它实际上会将其解释为 then(null),这就会导致前一个 Promise 的结果会穿透下面。
六、Promise的应用场景
...
学习写作中,持续补充更新,记录不好的地方望指出修改,共同进步~
参考资料: