彻底搞定Promise

Posted nan-prototype

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了彻底搞定Promise相关的知识,希望对你有一定的参考价值。

<!doctype html>promise

promise原因

简单来说 就是为了解决callback hell 简单来看一个回调的例子

 
 
 
?x
 
 
 
 
1
let fs = require(‘fs‘)
2
fs.readFile(‘./a.txt‘,‘utf8‘,function(err,data){
3
  fs.readFile(data,‘utf8‘,function(err,data){
4
    fs.readFile(data,‘utf8‘,function(err,data){
5
      console.log(data)
6
    })
7
  })
8
})
9
?
 
 

 

由于括号过多过于复杂很难判断具体 某函数回调是哪一个。在引入了promise后 事情就变得简单了很多了。

 
 
 
xxxxxxxxxx
19
 
 
 
 
1
let fs = require(‘fs‘)
2
function read(url){
3
  return new Promise((resolve,reject)=>{
4
    fs.readFile(url,‘utf8‘,function(error,data){
5
      error && reject(error)
6
      resolve(data)
7
    })
8
  })
9
}
10
?
11
read(‘./a.txt‘)
12
  .then(data=>{
13
  return read(data)})
14
  .then(data=>{
15
  return read(data)})
16
  .then(data=>{
17
  console.log(data)
18
})
19
?
 
 

 

手写简单promise

promise标准

首先我们要知道自己手写一个Promise,应该怎么去写,谁来告诉我们怎么写,需要遵循什么样的规则。当然这些你都不用担心,其实业界都是通过一个规则指标来生产Promise的。让我们来看看是什么东西。Promise/A+

 

Constructor

Constructor表示构造函数,先来实现一个最简单的promise。分析一下,最简单的promise包含以下几种主要功能

1.执行函数

2.reslove[作为成功调用时候的回调函数]

3.reject [作为失败调用时候的回调函数]

那么简单来实现的话 应该是下面这种框架

 
 
 
xxxxxxxxxx
7
 
 
 
 
1
new promise = 
2
function (执行函数 ) 
3
{
4
reslove ()
5

6
reject  ()
7
}
 
 

Okk 那么现在按照ES6 类的方法来完善上面的伪代码:

 
 
 
xxxxxxxxxx
35
 
 
 
 
1
class Promise{
2
  constructor(executor){
3
    //控制状态,使用了一次之后,接下来的都不被使用
4
    this.status = ‘pendding‘
5
    this.value = undefined
6
    this.reason = undefined
7
    
8
    //定义resolve函数
9
    let resolve = (data)=>{
10
      //这里pendding,主要是为了防止executor中调用了两次resovle或reject方法,而我们只调用一次
11
      if(this.status===‘pendding‘){
12
        this.status = ‘resolve‘
13
        this.value = data
14
      } 
15
    }
16
?
17
    //定义reject函数
18
    let reject = (data)=>{
19
      if(this.status===‘pendding‘){
20
        this.status = ‘reject‘        
21
        this.reason = data
22
      } 
23
    }
24
?
25
    //executor方法可能会抛出异常,需要捕获
26
    try{
27
      //将resolve和reject函数给使用者      
28
      executor(resolve,reject)      
29
    }catch(e){
30
      //如果在函数中抛出异常则将它注入reject中
31
      reject(e)
32
    }
33
  }
34
}
35
?
 
 

then 方法

如果要说promise的话,大部分人的第一印象都是then回调。可见then方法对于promise的重要性。

简单来说,then的功能就是,传入成功和失败两种情况下的回调函数,then方法出现,便于promise的更简洁使用,其基础功能代码也很简单:

 
 
 
xxxxxxxxxx
13
 
 
 
 
1
then(onFufilled,onRejected)
2
{  
3
  
4
  if(this.status === ‘resolve‘){
5
    onFufilled(this.value)
6
  }
7
  
8
  if(this.status === ‘reject‘){
9
    onRejected(this.reason)
10
  }
11
  
12
}
13
?
 
 

onFulfilled 特性 如果 onFulfilled 是函数:

当 promise 执行结束后其必须被调用,其第一个参数为 promise 的终值

在 promise 执行结束前其不可被调用

其调用次数不可超过一次

onRejected 特性 如果 onRejected 是函数:

当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的据因

在 promise 被拒绝执行前其不可被调用

其调用次数不可超过一次

 

异步的Promise实现

刚刚其实只能应对同步的情况,异步的时候就需要借助callback来进行实现了,当reslovereject被执行后 去执行对应的callback

 
 
 
xxxxxxxxxx
20
 
 
 
 
1
//存放成功回调的函数
2
this.onResolvedCallbacks = []
3
//存放失败回调的函数
4
this.onRejectedCallbacks = []
5
?
6
let resolve = (data)=>{
7
  if(this.status===‘pendding‘){
8
    this.status = ‘resolve‘
9
    this.value = data
10
    //监听回调函数
11
    this.onResolvedCallbacks.forEach(fn=>fn())
12
  } 
13
}
14
let reject = (data)=>{
15
  if(this.status===‘pendding‘){
16
    this.status = ‘reject‘        
17
    this.reason = data
18
    this.onRejectedCallbacks.forEach(fn=>fn())
19
  } 
20
}
 
 

 

那么对于then我们也需要添加一些东西来进行适配,判断当Promise是一步操作时候,需要在我们之前定义的回调函数组中添加一个回调函数

 
 
 
xxxxxxxxxx
12
 
 
 
 
1
if(this.status === ‘pendding‘){
2
  this.onResolvedCallbacks.push(()=>{
3
    // to do....
4
    let x = onFufilled(this.value)
5
    resolvePromise(promise2,x,resolve,reject)
6
  })
7
  this.onRejectedCallbacks.push(()=>{
8
    let x = onRejected(this.reason)
9
    resolvePromise(promise2,x,resolve,reject)
10
  })
11
}
12
?
 
 

 

无限then的秘籍

这也是Promise中的重头戏,我们在用Promise的时候可能会发现,当then函数中return了一个值,我们可以继续then下去,不过是什么值,都能在下一个then中获取,还有,当我们不在then中放入参数,例:promise.then().then(),那么其后面的then依旧可以得到之前then返回的值,来看看具体的实现吧

 
 
 
xxxxxxxxxx
65
 
 
 
 
1
then(onFufilled,onRejected){ 
2
    //解决onFufilled,onRejected没有传值的问题
3
    onFufilled = typeof onFufilled === ‘function‘?onFufilled:y=>y
4
    //因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后then的resolve中捕获
5
    onRejected = typeof onRejected === ‘function‘?onRejected:err=>{ throw err ;}
6
    //声明一个promise对象
7
    let promise2
8
    if(this.status === ‘resolve‘){
9
      //因为在.then之后又是一个promise对象,所以这里肯定要返回一个promise对象
10
      promise2 = new Promise((resolve,reject)=>{
11
        setTimeout(()=>{
12
          //因为穿透值的缘故,在默认的跑出一个error后,不能再用下一个的reject来接受,只能通过try,catch        
13
          try{
14
            //因为有的时候需要判断then中的方法是否返回一个promise对象,所以需要判断
15
            //如果返回值为promise对象,则需要取出结果当作promise2的resolve结果
16
            //如果不是,直接作为promise2的resolve结果
17
            let x = onFufilled(this.value)
18
            //抽离出一个公共方法来判断他们是否为promise对象
19
            resolvePromise(promise2,x,resolve,reject)
20
          }catch(e){
21
            reject(e)
22
          }
23
        },0)
24
      })
25
    }
26
    if(this.status === ‘reject‘){
27
      promise2 = new Promise((resolve,reject)=>{
28
        setTimeout(()=>{
29
          try{
30
            let x = onRejected(this.reason)
31
            resolvePromise(promise2,x,resolve,reject)
32
          }catch(e){
33
            reject(e)
34
          }
35
        },0)
36
      })
37
    }
38
    if(this.status === ‘pendding‘){
39
      promise2 = new Promise((resolve,reject)=>{
40
        this.onResolvedCallbacks.push(()=>{
41
          // to do....
42
          setTimeout(()=>{
43
            try{
44
              let x = onFufilled(this.value)
45
              resolvePromise(promise2,x,resolve,reject)
46
            }catch(e){
47
              reject(e)
48
            }
49
          },0)
50
        })
51
        this.onRejectedCallbacks.push(()=>{
52
          setTimeout(()=>{
53
            try{
54
              let x = onRejected(this.reason)
55
              resolvePromise(promise2,x,resolve,reject)
56
            }catch(e){
57
              reject(e)
58
            }
59
          })
60
        })
61
      })
62
    }
63
    return promise2   //首先要确保then返回值为promise 才可以保证then的链式循环
64
  }
65
?
 
 

 

1.返回Promise?:首先我们要注意的一点是,then有返回值,then了之后还能在then,那就说明之前的then返回的必然是个Promise

2.为什么外面要包一层setTimeout?:因为Promise本身是一个异步方法,属于微任务一列,必须得在执行栈执行完了在去取他的值,所以所有的返回值都得包一层异步setTimeout。

3.为什么开头有两个判断?:这就是之前想要解决的如果then函数中的参数不是函数,那么我们需要做处理。如果onFufilled不是函数,就需要自定义个函数用来返回之前resolve的值,如果onRejected不是函数,自定义个函数抛出异常。这里会有个小坑,如果这里不抛出异常,会在下一个then的onFufilled中拿到值。又因为这里抛出了异常所以所有的onFufilled或者onRejected都需要try/catch,这也是Promise/A+的规范。当然本人觉得成功的回调不需要抛出异常也可以,大家可以仔细想想。

4.resolvePromise是什么?:这其实是官方Promise/A+的需求。因为你的then可以返回任何职,当然包括Promise对象,而如果是Promise对象,我们就需要将他拆解,直到它不是一个Promise对象,取其中的值。

它的作用是用来将onFufilled的返回值进行判断取值处理,把最后获得的值放入最外面那层的Promise的resolve函数中。

 

reslovepromise

 
 
 
xxxxxxxxxx
39
 
 
 
 
1
function resolvePromise(promise2,x,resolve,reject){
2
  //判断x和promise2之间的关系
3
  //因为promise2是上一个promise.then后的返回结果,所以如果相同,会导致下面的.then会是同一个promise2,一直都是,没有尽头
4
  if(x === promise2){//相当于promise.then之后return了自己,因为then会等待return后的promise,导致自己等待自己,一直处于等待
5
    return reject(new TypeError(‘循环引用‘))
6
  }
7
  //如果x不是null,是对象或者方法
8
  if(x !== null && (typeof x === ‘object‘ || typeof x === ‘function‘)){
9
    //为了判断resolve过的就不用再reject了,(比如有reject和resolve的时候)
10
    let called
11
    try{//防止then出现异常,Object.defineProperty
12
      let then = x.then//取x的then方法可能会取到{then:{}},并没有执行
13
      if(typeof then === ‘function‘){
14
        //我们就认为他是promise,call他,因为then方法中的this来自自己的promise对象
15
        then.call(x,y=>{//第一个参数是将x这个promise方法作为this指向,后两个参数分别为成功失败回调
16
          if(called) return;
17
          called = true
18
          //因为可能promise中还有promise,所以需要递归
19
          resolvePromise(promise2,y,resolve,reject)
20
        },err=>{
21
          if(called) return;
22
          called = true
23
          //一次错误就直接返回
24
          reject(err)
25
        })
26
      }else{
27
        //如果是个普通对象就直接返回resolve作为结果
28
        resolve(x)
29
      }
30
    }catch(e){
31
      if(called) return;
32
      called = true
33
      reject(e)
34
    }
35
  }else{
36
    //这里返回的是非函数,非对象的值,就直接放在promise2的resolve中作为结果
37
    resolve(x)
38
  }
39
}
 
 

 

promise全完善

catch

简单来说,就是用来捕获Promise中rejecet的值,也就是相当于then方法中的OnRejected回调函数

 
 
 
xxxxxxxxxx
3
 
 
 
 
1
catch(onRejected){
2
  return this.then(null,onRejected)
3
}
 
 

该方法是挂在Promise原型上的方法。当我们调用catch传callback的时候,就相当于是调用了then方法。

 

reslove/reject

对于Promsie.reslovePromise.reject来讲,它本质是返回一个Promise对象

 
 
 
xxxxxxxxxx
13
 
 
 
 
1
//resolve方法
2
Promise.resolve = function(val){
3
  return new Promise((resolve,reject)=>{
4
    resolve(val)
5
  })
6
}
7
//reject方法
8
Promise.reject = function(val){
9
  return new Promise((resolve,reject)=>{
10
    reject(val)
11
  })
12
}
13
?
 
 

 

all

all方法可以说是Promise中很常用的方法了,它的作用就是将一个数组的Promise对象放在其中,当全部resolve的时候就会执行then方法,当有一个reject的时候就会执行catch,并且他们的结果也是按着数组中的顺序来排放的,那么我们来实现一下。

 
 
 
xxxxxxxxxx
20
 
 
 
 
1
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
2
Promise.all = function(promises){
3
  let arr = []
4
  let i = 0
5
  function processData(index,data){
6
    arr[index] = data
7
    i++
8
    if(i == promises.length){
9
      resolve(arr)
10
    }
11
  }
12
  return new Promise((resolve,reject)=>{
13
    for(let i=0;i<promises.length;i++){
14
      promises[i].then(data=>{
15
        processData(i,data)
16
      },reject)
17
    }
18
  })
19
}
20
?
 
 

 

其原理就是将参数中的数组取出遍历,每当执行成功都会执行processData方法,processData方法就是用来记录每个Promise的值和它对应的下标,当执行的次数等于数组长度时就会执行resolve,把arr的值给then。这里会有一个坑,如果你是通过arr数组的长度来判断他是否应该resolve的话就会出错,为什么呢?因为js数组的特性,导致如果先出来的是1位置上的值进arr,那么0位置上也会多一个空的值,所以不合理

 

 
 
 
xxxxxxxxxx
20
 
 
 
 
1
Promise.all = function (p) {
2
  let arr = []
3
  let i = 0
4
  function processDate(index, data) {
5
    arr[index] = data
6
    i++
7
    if (i == p.length) {
8
      resolve(arr)
9
    }
10
  }
11
?
12
  return new Promise((resolve, reject) => {
13
    for (let i = 0; i < p.length; i++) {
14
      p(i).then (data => {
15
        processDate(i,data)
16
      },reject)
17
    }
18
  })
19
}
20
?
 
 

 

race

race方法虽然不常用,但是在Promise方法中也是一个能用得上的方法,它的作用是将一个Promise数组放入race中,哪个先执行完,race就直接执行完,并从then中取值。我们来实现一下吧。

 
 
 
xxxxxxxxxx
7
 
 
 
 
1
Promise.race = function(promises){
2
  return new Promise((resolve,reject)=>{
3
    for(let i=0;i<promises.length;i++){
4
      promises[i].then(resolve,reject)
5
    }
6
  })
7
}



 

 

以上是关于彻底搞定Promise的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段12——JavaScript的Promise对象

八段代码彻底掌握 Promise

#yyds干货盘点#这一次,彻底搞懂Promise

前端面试题之手写promise

这一次,彻底弄懂 Promise 原理

彻底理解Javascript 中的 Promise