深入理解Promise

Promise是ES6中的一个标准了,我们使用一般用Promise来进行网络数据请求

下面总结一下Promise的用法和实现:

1.Promise的用法

1.使用Promise类包装一个异步的请求,一般是数据请求,可以用在网络请求,数据库请求,IO读写等等只要是异步的请求都行,比如我们用redis和mysql可以把数据库返回的数据包装成Promise对象,我们用ajax或者原生fetch或者axios发送http请求。比如我们用axios库,封装了一个get方法,

export const get = (url, params) => {
  return new Promise((resolve, reject) => {
    axios.get(queryString(`${baseUrl}/api${url}`, params))
      .then(resp => {
        resolve(resp.data)
      }).catch(reject)
  })
}

export const post = (url, data) => {
  return new Promise((resolve, reject) => {
    axios.post(`${baseUrl}/api${url}`, data)
      .then(resp => {
        resolve(resp.data)
      })
      .catch(reject)
  })
}

然后我们还可以继续对这个get方法封装成更高级的方法,在不同的页面里,我们都需要这个get方法,我们可以根据不同页面的需要,封装不同的fetchdata函数:

 @action fetchTopics(tab) {
    return new Promise((resolve, reject) => {
      if (tab === this.tab && this.topics.length > 0) {
        resolve()
      } else {
        this.tab = tab
        this.topics = []
        this.syncing = true
        get('/topics', {
          mdrender: false,
          tab,
        }).then(resp => {
          if (resp.success) {
            const topics = resp.data.map(topic => {
              return new Topic(createTopic(topic))
            })
            this.topics = topics
            this.syncing = false
            resolve()
          } else {
            this.syncing = false
            reject()
          }
        }).catch(err => {
          reject(err)
        })
      }
    })
  }

然后我们还是返回一个Promise对象,在需要使用的地方,我们使用then方法去解析,这里我们没有resolve出数据,因为我们这里是获取数据,然后保存到Mobx中的state里,所以我们在get后的then里,直接办response的data赋值给this.topics。这里的topic是observable,相当于我们在这里完成数据的获取,直接resolve()空就可以了,在ComponentDIdMount中,组件挂载的时候,我们调用一下这个函数就可以完成数据的获取了,在组件里,我们从this.props.topicStore.topics就可以获取到这些数据了, 然后解析对象,进行React渲染

Promise还可以用来包装mysql和redis的数据,为调用con.query这个mysql包提供的mysql查询方法返回的数据包装个Promise,

在需要用到的地方,使用then解析


//第一步,mysql查询,返回Promise包装的数据
function exec(sql) {
    const promise = new Promise((resolve, reject) => {
        con.query(sql, (err, result) => {
            if (err) {
                reject(err)
                return
            }
            resolve(result)//将promise.value = result,promise.status = 'fulfiiled'
        })
    })
    return promise
}
//第二步,controller层,拼接mysql语句,返回特定的mysql语句查询的Promise结果



const getDetail = (id) => {
    const sql = `select * from blogs where id='${id}'`
    return exec(sql).then(rows => {
        return rows[0]
   })
}
//第三步,不同路由下,传参不同,再包装一次做路由处理
    if (method === 'GET' && req.path === '/api/blog/detail') {
        // const data = getDetail(id)
        // return new SuccessModel(data)
        const result = getDetail(id)
        return result.then(data => {
            return new SuccessModel(data)
        })
    }
//第四步,主serverhandle中,完成数据的最后处理
 const blogResult = handleBlogRouter(req, res)
        if (blogResult) {
            blogResult.then(blogData => {
                if (needSetCookie) {
                    res.setHeader('Set-Cookie', `userid=${userId}; path=/; httpOnly; expires=${getCookieExpires()}`)
                }

                res.end(
                    JSON.stringify(blogData)
                )
            })
            return
        }

我们看到,从最开始的原始数据,用Priomise包装后,我们每次都使用then方法去链式调用之前一个then方法处理过的数据,从原始的mysql的结果,到取出rows[0],再到用SuccessModel再做一次包装,再到最后使用then对上一个then返回的model做最后的response处理,结果response请求,最后不再return出数据,我们就走完了一个完整数据获取,进一步处理包装,再到返回给客户端,这样一个流程,多亏了Promise的then的链式调用(then方法会return this,后面会讲实现),我们可以很方便的使用promise做异步处理

同样我们也使用catch和reject进行错误的处理,如果一些response中有err的字段,我们可以检测,然后reject出去。

catch则是检测一些throw 的error,那么catch和reject的关系是啥呢,then方法其实有两个参数,第二个参数为啥一般没有用呢

我们看到,then可以实现链式的调用,resolve传入的参数可以被then方法继续调用,相比这个data肯定是赋值给了这个Promise实例的一个属性,比如;this.data,这些原理如何实现的呢,Primise还有race和all方法,这些方法又有什么用呢?Promise到底是怎么实现的呢,接下来介绍Promise的实现。

2.Promise的实现

1.Promise对象的基本结构,

new Promise((resolve,reject) => {}),

实例化一个Promise对象的时候,我们需要传入一个回调函数,resolve和reject是Promise类为回调注入的两个函数参数

,在回调里,我们会进行异步函数的处理,这里我们会使用一个 typeof 运算符,在Promise的构造函数里检测传入的是不是函数

Class Promise {
   constructor(handle) {
    if(typeof handle !== 'function'){
        throw new Error ('Promise must accept a function as a parameter')
        }
    } 
}

2.Promise的基本状态,Promise有三种状态,这是PromiseA+定义的,分别是:pending(进行中) fulfilled(成功) rejected(失败)promise的状态只能由pending编程fulfiied和rejected,改变后就不再发生变化,自然当我们调用resolve的时候,我们知道这个时候是成功了,我们就会把状态由pending改成fulfilled,reject方法则会改成rejected

class MyPromise {
  constructor(handle) {
    if(typeof handle !== 'function')
      throw new Error('must accept a function as a parameter')
    this.status = 'pending';
    this.value = undefined;
    //类的方法中使用了this,并且没有使用箭头函数的表示,那肯定要使用bind避免隐式绑定this丢失
    try{
      handle(this.resolve.bind(this),this.reject.bind(this))
    } catch(err) {
      this.reject(err)
    }

  }

  resolve(val) {
    if(this.status !== pending) return
    this.status = 'fulfilled';
    this.value  = val;
  }

  reject(val) {
    if(this.status !== pending ) return
    this.status = 'rejected';
    this.value = val;
  }
}

3.then方法,then方法是有两个参数的!只不过一般我们只用第一个,第一个回调就是处理onfulfilled的,当status变为成功的时候,我们就调用then方法传入的第一个回调,变成rejeced的时候调用第二个回调,then方法可以调用多次,所以我们肯定要维护两个队列用来处理状态变成fulfilled和rejected时候的回调函数们,然后为了接下来可以继续调用,我们要返回一个新的promise对象。首先我们要添加两个回调函数队列到构造函数中,对应的是fulfilled和rejected,

this.fuifilledQueues = []
this.rejectedQueues = []

接着我们添加then方法:

then(onFulfilled,onRejected) {
    //从当前promise实例中解析出value和status,传入两个函数参数,第一个是成功回调,第二个是失败回调
    const {value ,status} = this;
    switch(status) {
      case 'pending':
        this.fulfilledQueue.push(onFulfilled)
        this.rejectedQueue.push(onRejected)
      case 'fulfilled':
        onFulfilled(value)
        break
      case 'rejected':
        onRejected(value)
        break
    }
  
   return new MyPromise((onFulfilledNext,onRejectedNext) => {})
  } 

返回一个新的promise了,那新的promise的状态是啥呢?这个状态就依赖于当前then方法执行的回调的情况和返回值,所以我们做一些完善。我们return出的Priomise也接受同样的两个回调,在这里就是两个Next

class MyPromise {
  constructor(handle) {
    if(typeof handle !== 'function')
      throw new Error('must accept a function as a parameter')
    this.status = 'pending';
    this.value = undefined;
    this.fulfilledQueue = [];
    this.rejectedQueue = [];
    //类的方法中使用了this,并且没有使用箭头函数的表示,那肯定要使用bind避免隐式绑定this丢失
    try{
      handle(this.resolve.bind(this),this.reject.bind(this))
    } catch(err) {
      this.reject(err)
    }

  }

  resolve(val) {
    if(this.status !== pending) return
    this.status = 'fulfilled';
    this.value  = val;
  }

  reject(val) {
    if(this.status !== pending ) return
    this.status = 'rejected';
    this.value = val;
  }

  then(onFulfilled,onRejected) {
    //从当前promise实例中解析出value和status,传入两个函数参数,第一个是成功回调,第二个是失败回调
    const {value ,status} = this;
    return new MyPromise((onFulfilledNext,onRejectedNext) => {
    let fulfilled = value => {
      try{
        if(typeof onFulfilled !== 'function'){
          onFulfilledNext(value)
        } else {
          let res = onFulfilled(value);
          if(res instanceof MyPromise) {
            res.then(onFulfilledNext,onRejectedNext)
          } else {
            onFulfilledNext(res);
          }
        }
      } catch (err) {
        onRejectedNext(err)
      }
    }

    let rejected = error => {
      try{
        if(typeof onRejected !== 'function') {
          onRejectedNext(error)
        } else {
          let res = onRejected(error);
          if(res instanceof MyPromise) {
            res.then(onFulfilledNext,onRejectedNext)
          } else {
            onFulfilledNext(res)
          }
        }
      } catch(err) {
        onRejectedNext(err)
      }
    }
    switch(status) {
      case 'pending':
        this.fulfilledQueue.push(onFulfilled)
        this.rejectedQueue.push(onRejected)
      case 'fulfilled':
        onFulfilled(value)
        break
      case 'rejected':
        onRejected(value)
        break
    }
  })
  }
}

然后我们在resolve和reject也要改成把队列里的回调都至此执行

_resolve (val) {
  const run = () => {
    if (this._status !== PENDING) return
    this._status = FULFILLED
    // 依次执行成功队列中的函数,并清空队列
    const runFulfilled = (value) => {
      let cb;
      while (cb = this._fulfilledQueues.shift()) {
        cb(value)
      }
    }
    // 依次执行失败队列中的函数,并清空队列
    const runRejected = (error) => {
      let cb;
      while (cb = this._rejectedQueues.shift()) {
        cb(error)
      }
    }
    /* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
      当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
    */
    if (val instanceof MyPromise) {
      val.then(value => {
        this._value = value
        runFulfilled(value)
      }, err => {
        this._value = err
        runRejected(err)
      })
    } else {
      this._value = val
      runFulfilled(val)
    }
  }
  // 为了支持同步的Promise,这里采用异步调用
  setTimeout(run, 0)
}

然后还有两个特殊的静态方法,一个是race一个是all

all方法是一个promise对象组成的数组,必须每个都成功,才返回冲的结果数组,否则只要一个失败,就直接reject出那个Promise的失败返回值

  static all (list) {
    return new Promise((resolve,reject) => {
      let values = [];
      let count = 0;
      for(let [i,promise] of list.entries()){
        this.resolve(p).then(res => {
          values[i] = res;
          count++
          if(count === list.length) resolve(values)
        },err => {
          reject(err)
        })
      }

    })
  }

race方法,只要有一成功或者失败,就直接resolve或者reject

static race(list ) {
    return new Promise ((resolve,reject) => {
      for(let p of list) {
        this.resolve(p).then((res) => {
          resolve(res )
        },err => {
          reject(err)
        }) 
      }
    })
  }

还有一个就是finally方法,用于不管Promise最后状态,都会执行的方法

  finally(cb) {
    return this.then(
      value => MyPromise.resolve(cb()).then(() => value),
      reason => MyPromise.resolve(cb()).then(() => {throw reason})
    )
  }

以上加起来就是一个完整的Promise啦

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值