Promise对象

首先先理解如下几个概念的概念:git

同步:一次只能执行一次任务,这个任务执行完以后才能执行下一个,它会阻塞其余任务。es6

异步:能够一块儿执行多个任务。github

回调地狱:回调函数的层层嵌套,致使代码层次过多,很差理解和维护。面试

1、Promise的含义

先简单了解如下Promise的含义和特性:编程

  • Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更加合理和强大;segmentfault

  • 所谓的Promise,简单来讲就是一个容器,里面保存着某个将来才会结束的事件(一般是一个异步操做)的结果;数组

  • 从语法上来讲,Promise是一个对象,从它能够获取异步操做的消息;promise

  • Promise对象表明的是一个异步操做,有三种状态: pending(等待状态)、 fulfilled(成功状态)、 rejected(失败状态)markdown

Promise对象的特色:

1. 对象的状态不收外界的影响;并发

只有异步操做的结果才能决定当前是哪种状态,任何其余操做都没法改变这个状态。

2. 一旦状态改变就不会再变;

Promise对象状态改变只有两种可能:

  • 从pending变为fulfilled
  • 从pengding变为rejected

只要这两种状态发现了,状态就凝固了,不会再变了,会一直保持这个结果,这时称为resolved(已定型)

3. 每一个Promise实例都有一个then方法,一个Promise能够.then多个;

4. 每次执行Promise的时候,都会返还一个新的Promise实例;

有了Promise对象,就能够将异步操做以同步操做的流程表达出来,解决了异步层层嵌套的函数(简称回调地狱)问题。

说到 Promise,咱们首先想到的最核心的功能就是异步链式调用。

Promise对象的缺点:

  1. 没法取消Promise。一旦新建它就会当即执行,没法中途取消;

  2. 若是不设置回调函数,Promise内部抛出错误,不会反应到外部;

  3. 当处于Pending(进行中)状态时,没法得知目前进展到哪个节点(刚开始仍是即将完成);

使用Promise对象的优势:

  1. 能够解决异步嵌套问题(回调地狱);
  2. 能够解决多个异步并发问题;

2、基本用法

注意,为了行文方便,本章后面的resolved统一只指fulfilled状态,不包含rejected状态。

一、创造一个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
复制代码

二、实例接受两个参数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: 内部抛出错误
// ...
复制代码

3、Promise实例的原理

一、手写一个基础版的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失败函数:

img

接下来开始调用then( )方法,then能够放置了两个函数:

  • onfulfilled 实例成功要执行的逻辑
  • onrejected 实例失败要执行的逻辑

那么何时调用哪个方法呢?这要根据Promise实例的状态来判断了:

img

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;//导出
复制代码

5、常见的Promise面试题

一、实现函数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秒亮一次;如何让三个灯不断交替重复亮灯?(用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();
  })
  
})
复制代码

三、输出下面的执行结果:

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 的结果会穿透下面。

6、Promise的应用场景

...

学习写做中,持续补充更新,记录很差的地方望指出修改,共同进步~

参考资料:

ECMAScript 6 入门-Promise

关于Promise的面试题

手写Promise20行

剖析Promise内部结构,一步一步实现一个完整的、能经过全部Test case的Promise类

手写Promise

相关文章
相关标签/搜索