前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Promise原理解析与实现

Promise原理解析与实现

作者头像
前朝楚水
发布2018-04-03 14:48:52
7880
发布2018-04-03 14:48:52
举报
文章被收录于专栏:互联网杂技互联网杂技

这里说的Promise是es6 harmony的Promise, 而非那个DOM Promise. 现在的Chrome两种Promise都支持, 但默认为DOM的Promise, 要想打开harmony模式, 还得要在chrome://flag中打开harmony (启用实验性 JavaScript)

分辨dom promise和harmony promise的方法就是在dev中输入Promise(function(){})

如果报错了说明是dom的promise, 不报错则为harmony的promise

因为dom的promise标准已经被删除, 而harmony的promise既可以在浏览器中用又可以将来在nodejs中用, 我们当然是选harmony的promise啦

现在我们来尝试用100行左右代码实现一下promise的大概功能

首先写出主要的Promise函数

代码语言:javascript
复制
function Promise(resolver) {
  resolver(resove, reject)}

我们都知道Promise的参数是一个函数, 其参数是promise内部控制流程的resolve和reject

看到这里, 想必大家觉得很熟悉, 所有流控制的库貌似都是传一个表达 继续往下传的内部函数, 说大白话就是 我这里搞定了, 你继续 的回调函数

比如express4之前用到的connect, 其中的app.*()中的function第三个参数就是next, 可以用来移至下一个路由栈继续匹配, 而promise则使用了两个内部函数, 一个表达流程正确的resolve(解决了), 另一个是流程失败的reject(拒绝了)

虽然外观略不同, 但不管是connect还是promise, 其内部都有一个stack或者queue的东西保存着全部的流, 在js中显然也就是一个数组

比如express中可以这么链式的写

代码语言:javascript
复制
app.use(function(req, res, next) {
  next()}).get('/xxx', function(req, res, next) {
  next()}).use(function() {})

其整个路由栈都被存入一个数组, 在next的时候移到下一个

而Promise的链式用法则为

代码语言:javascript
复制
// 先封装一个返回promise的函数function delay(time) {
  return new Promise(function(resolve) {
    setTimeout(resolve, time)
  })}delay(100).then(function() {
  return delay(200)}).then(function() {
  return delay(300)})

promise的链式由then中的resolve返回值加入, 而非一开始就全部塞入, 这就是promise和express中next的主要区别

继续试着实现promise

代码语言:javascript
复制
function Promise(resolver) {
  resolver(resolve, reject)
  var queue = [] // 保存链式调用的数组
  this.then = function(resolve, reject) {
    queue.push([resolve, reject]) // 把then中的resolve和reject都存起来
  }}

我们还没有写resolve和reject这两个内部函数呢, 这俩函数作用完全一样, 只不过一个表示正确, 一个表示错误, 我们完全可以用一个类似connect中的next来表达这两个函数

代码语言:javascript
复制
function resolve(x) {
  next(0, x) // 用0来告诉next是resolve}function reject(resson) {
  next(1, reason) // 用1告诉next是reject}

那这个控制流程的next到底该啥样呢, 我们都知道, next的作用不过是去调用queue中下一个函数而已

代码语言:javascript
复制
function next(i, val) {
  // i 仅仅是用来区分resolve还是reject, val是值
  while (queue.length) {
    var arr = queue.shift() // 移出一个resolve和reject对, 也就是[resolve, reject]
    arr[i](val) // 执行之, val是唯一参数
  }}

为何要while呢? 因为promise可以不停的then下去, 只不过传下来的都是resolve中的值都是undefined罢了, 因此我们用while来用光全部的resolve

问题就来了, 如果我们这么写

代码语言:javascript
复制
var p = new Promise(function(resolve) {
  resolve('ok')})p.then(function(x) {
  console.log(x)})

因为完全没有延迟, 显然resolve先走了, 而resolve执行的时候, queue中还没有函数去接它, 这个时候就then就不可能触发了

因此要么把resolve的值存起来, 要么就是让resolve肯定晚于后面的then执行

我这里偷一下懒, 用一下setTimeout

代码语言:javascript
复制
function(i, val) {
  setTimeout(function() {
    while (queue.length) {
      var arr = queue.shift()
      arr[i](val)
    }
  })}

这样resolve的出栈动作就肯定比进栈晚了, 不过这样写虽然很简洁, 但肯定有隐患(只不过我还没发现)

那如何让Promise支持链式调用呢? 这也不难, 我们只需将其执行结果存起来, 帮它then下去即可

代码语言:javascript
复制
function next(i, val) {
  setTimeout(function() {
    while (queue.length) {
      var arr = queue.shift()
      if (typeof arr[i] === 'function') {
        try {
          var chain = arr[i](val)
        } catch (e) {
          return reject(e)
        }
        if (chain && typeof chain.then === 'function') {
          // 一般来说链式的话resolve返回值为一个promise对象
          // 所谓promise对象, 其实不过是 {then: function() {} }
          // 也就是一个含有then函数的对象
          return chain.then(resolve, reject)
        } else {
          // 注意, 此处resolve中同样可以返回一个普通值
          // 我们帮他包装成promise对象即可
          return Promise.resolved(chain).then(resolve, reject)
        }
      }
    }
  })}

上面是一个加上错误处理的next函数, 错误处理在promise中, 就是转成reject即可


其它函数

Promise还有其它函数, 比如Promise.all, Promise.resolved

我至今都不知道是Promise.resolved还是Promise.resolve

不过我觉得resolved听上去更对一点 (一个已经解决的承诺) , 而且chrome中也是这样的

实现这些附属函数特别简单

代码语言:javascript
复制
Promise.resolved = Promise.cast = function(x) {
  return new Promise(function(resolve) {
    resolve(x)
  })}Promise.rejected = function(reason) {
  return new Promise(function(resolve, reject) {
    reject(reason)
  })}Promise.all = function(values) {
  var defer = Promise.deferred()
  var len = values.length
  var results = []
  values.forEach(function(p, i) {
    p.then(function(x) {
      results[i] = x
      len--
      if (len === 0) {
        defer.resolve(results)
      }
    }, function(r) {
      defer.reject(r)
    })
  })
  return defer.promise}

这里的all用到了一个Promise.deferred的函数, 这个函数格外重要


Promise.deferred

deferred的实现同样不难, 但其使用概率则是大大的, 可能比直接用Promise的几率还大

代码语言:javascript
复制
Promise.deferred = function() {
  var result = {}
  result.promise = new Promise(function(resolve, reject) {
    result.resolve = resolve
    result.reject = reject
  })
  return result}

deferred的使用方法非常顺手

代码语言:javascript
复制
var def = Promise.deferred()setTimeout(function() {
  def.resolve(222)}, 1000)def.promise.then(function(x) {
  console.log(x)})

看到def, 才能看到Promise的精髓, 甚至jQuery反而提供defer作为主对象, promise不过是附属对象

我的完整Promise在这里

虽然目前Promise还不到100行, 但真正实现起来, 要比co那样借助yield的异步框架混淆很多, 我已经改了很多次, 但仍有bug, 这当然也跟我最近老打飞机有关, 已经有点神志不清

但是yield估计几年后才能用, 因此趁早学会Promise还是有必要滴

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-07-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 交互设计前端开发与后端程序设计 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 其它函数
  • Promise.deferred
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档