资讯专栏INFORMATION COLUMN

前端杂谈: 如何实现一个 Promise?

forsigner / 2531人阅读

摘要:并在其中判断当前状态。如果在异步调用完成之后才被调用方法,则我们无法把异步调用的结果传递给回调函数。我们对可能出现的异常进行捕获,并将异常传递给。该实现的功能较为简陋,仅实现了部分规范。

前端杂谈: 如何实现一个 Promise? 首先, 什么是 Promise?
A promise is an object that may produce a single value some time in the future: either a resolved value, or a reason that it’s not resolved (e.g., a network error occurred). A promise may be in one of 3 possible states: fulfilled, rejected, or pending. Promise users can attach callbacks to handle the fulfilled value or the reason for rejection.

关键语句: Promise 是一个在将来某个时刻产生一个单一结果的对象.
通俗一点来说, Promise 代表了一个值, 但是这个值我们并不确定什么时候会被返回.

A promise is an object that may produce a single value some time in the future.
简单看看 Promise 的历史

Promise 在 1980 年代被创建出来 =>

在 1988 年正式得名: Promise =>

已经有很多人了解到了 Promise, 但是人们还是坚持使用 node.js 中提倡的以回调函数首个参数传 error 对象的方式处理异步代码. =>

Dojo 首次大规模的使用了 Promise , 相应的 Promise/A 被提出用以规范 Promise 的实现 =>

JQuery 开始使用 Promise 并真正使 Promise 广为人知 =>

JQuery 没有实现部分 Promise 的功能, 这也导致了 Promie/A+ 标准的产生 =>

ES6 正式引入了 Promise,并且和已有的实现了 Promise/A 规范的 library 相兼容 =>

实现 Promise 之前, 让我们看看 Promise 有哪些规范

Promise 是一个 thenable 对象, 也就是说 Promise 有一个 .then() 方法

一个 pending 状态的 Promise 可以进入 fulfilled 和 rejected 状态

promise 一旦进入 fulfilled 或 rejected 状态, 不可再改变其状态

一旦 promise 改变了其状态, 它笔芯有一个值(这个值也可能是 undefined)

开始实现一个 Promise

首先, 让我们看看一段最普通的异步代码:

// 异步方法定义
var basicAsyncFunc = function(callback) {
  setTimeout(function() {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) {
      callback(null, randomNumber)
    } else {
      callback(new Error("bad luck..."))
    }
  }, 1000)
}

// 异步方法调用
basicAsyncFunc((err, result) => {
  if (err) {
    console.log(`the reason fail is: ${err}`)
    return
  }
  console.log(`success get result: ${result}`)
})

按照 Promise 的规范定义, 理想中 Promise 的调用方式为:

// Promise 形式的异步方法定义
var promiseAsyncFunc = function() {}

// Promise 形式的异步方法调用
promiseAsyncFunc.then(
  data => {
    console.log(`success get result: ${data}`)
  },
  err => {
    console.log(`the reason fail is: ${err}`)
  }
)

按照这个理想当中的调用方式, 让我们写出第一版代码.

第一版 Promise:能保存回调方法
// Promise 形式的异步方法定义
var promiseAsyncFunc = function() {
  var fulfillCallback
  var rejectCallback

  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfillCallback(randomNumber)
    else rejectCallback(randomNumber)
  }, 1000)
  return {
    then: function(_fulfillCallback, _rejectCallback) {
      fulfillCallback = _fulfillCallback
      rejectCallback = _rejectCallback
    }
  }
}

// Promise 形式的异步方法调用
promiseAsyncFunc().then(fulfillCallback, rejectCallback)

我们的思路是在 .then() 方法中, 将 fullfill 和 reject 结果的回调函数保存下来, 然后在异步方法中调用. 因为是异步调用, 根据 event-loop 的原理, promiseAsyncFunc().then(fulfillCallback, rejectCallback) 传入的 callback 在异步调用结束时一定是已经赋值过了.

第二版 Promise:实构造函数

当前我们的实现 Promise 中,异步逻辑代码和 Promise 的代码是杂糅在一起的,让我们将其区分开:

var promiseAsyncFunc = function() {
  var fulfillCallback
  var rejectCallback

  return {
    fulfill: function(value) {
      if (fulfillCallback && typeof fulfillCallback === "function") {
        fulfillCallback(value)
      }
    },
    reject: function(err) {
      if (rejectCallback && typeof rejectCallback === "function") {
        rejectCallback(err)
      }
    },
    then: function(_fulfillCallback, _rejectCallback) {
      fulfillCallback = _fulfillCallback
      rejectCallback = _rejectCallback
    }
  }
}

let ownPromise = function(asyncCall) {
  let promise = promiseAsyncFunc()
  asyncCall(promise.fulfill, promise.reject)
  return promise
}

// Promise 形式的异步方法调用
ownPromise(function(fulfill, reject) {
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
})

我们新定义了一个方法 ownPromise() 用于创建 Promise,并在promiseAsyncFunc() 中暴露出 fulfillreject 接口方便异步代码去调用。

这里有一个问题,我们在调用 ownPromise()后得到了 promise 实例,此时我们可以直接调用 fulfill()reject()这两个方法,而理论上我们应该只应暴露 promise 的then()方法。所以我们利用闭包将这两个方法隐藏:

var promiseAsyncFunc = function() {
  var fulfillCallback
  var rejectCallback

  return {
    fulfill: function(value) {
      if (fulfillCallback && typeof fulfillCallback === "function") {
        fulfillCallback(value)
      }
    },
    reject: function(err) {
      if (rejectCallback && typeof rejectCallback === "function") {
        rejectCallback(err)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  asyncCall(defer.fulfill, defer.reject)
  return defer.promise
}

// Promise 形式的异步方法调用
ownPromise(function(fulfill, reject) {
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
})
第三版 Promise: 支持状态管理

为了实现规范中对于 Promise 状态变化的要求, 我们需要为 Promise 加入状态管理, 这一步较为简单, 让我们看代码:

const PENDING = Symbol("pending")
const FULFILLED = Symbol("fulfilled")
const REJECTED = Symbol("rejected")

// Promise 形式的异步方法定义
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback

  return {
    fulfill: function(value) {
      if (status !== PENDING) return
      if (typeof fulfillCallback === "function") {
        fulfillCallback(value)
        status = FULFILLED
      }
    },
    reject(error) {
      if (status !== PENDING) return
      if (typeof rejectCallback === "function") {
        rejectCallback(error)
        status = REJECTED
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  asyncCall(defer.fulfill, defer.reject)
  return defer.promise
}

// Promise 形式的异步方法调用
ownPromise(function(fulfill, reject) {
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
}).then(data => console.log(data), err => console.log(err))

这段代码中我们用到了 Symbol 来表示状态常量, 对 Symbol 不了解的同学可以看这里

为了判断 Promise 的状态, 我们加入了 fulfillreject 两个方法。并在其中判断 promise 当前状态。如果不是 pending 状态则直接 return(因为 Promise 状态只可能改变一次)。

现在我们的 promise 实现了对状态控制的规范:

只允许改变一次状态

只能从 pending => fulfilled 或 pending => rejected

但是我们的 Promise 有一个问题: promise 的值没有被保存下来。如果 promise 在异步调用完成之后才被调用 .then() 方法,则我们无法把异步调用的结果传递给回调函数。为此我们需要为 Promise 加一个 value 字段:

第四版 Promise: 保存异步调用的结果

我们为 promise 加入 value 字段,用于保存 Promise 的执行结果。

// Promise 形式的异步方法定义
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = _value
      status = FULFILLED
      if (typeof fulfillCallback === "function") {
        fulfillCallback(value)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = error
      status = REJECTED
      if (typeof rejectCallback === "function") {
        rejectCallback(error)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}

这里我们又发现一个问题,如果一个 Promise 已经是fulfillreject状态。我们再调用 then() 方法时,传入的回调方法永远不会被调用(因为 status 已经不是 pending)。

所以我们需要在 then()方法中对其状态进行判断:

// Promise 形式的异步方法定义
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = _value
      status = FULFILLED
      if (typeof fulfillCallback === "function") {
        fulfillCallback(value)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = error
      status = REJECTED
      if (typeof rejectCallback === "function") {
        rejectCallback(error)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        if (status === REJECTED) {
          _rejectCallback(value)
          return
        }
        if (status === FULFILLED) {
          _fulfillCallback(value)
          return
        }
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}
第五版 Promise: 支持链式调用

为了支持链式调用,.then() 方法的返回值必须是用 thenable (根据 Promise/A+ 规范, .then() 方法的返回值需要是一个的 Promise)

为此我们加入一个工具方法 makeThenable()。如果传入的 value 本身就有 then()方法,则直接返回 value。否则返回一个有 then()方法的对象。
在该对象的 then()方法中,我们根据 promise 的状态,调用不同的回调方法生成新的 value。

function makeThenable(value, status){
  if(value && typeof value.then === "function"){
    return value
  }
  if(status === FULFILLED){
    return {
      then: function(fulfillCallback, rejectCallback){
        return makeThenable(fulfillCallback(value), FULFILLED)
      }
    }
  }
  if(status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback){
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}

有了以上的 makeThenable()方法,我们可以在 promise 的fulfill()reject()回将 value 设置为 thenable:

var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = makeThenable(_value, FULFILLED) // 保证当前promise的value为 thenable
      status = FULFILLED
      if (typeof fulfillCallback === "function") {
        value.then(fulfillCallback)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = makeThenable(error, REJECTED) 、、  // 保证当前value为 thenable
      status = REJECTED
      if (typeof rejectCallback === "function") {
        value.then(null, rejectCallback)
      }
    },
    promise: {
      then: function(){}
    }
  }
}

接下来让我们看 then()方法。为了返回一个新的 promise,我们首先得创建一个新的 promise。其次当前 promise 在fulfill()reject()时,应该调用新的 promise 的fullfill()reject()方法。所以我们在将 fulfullCallbackrejectCallback赋值给当前 promise 时,将其包装一下。代码如下:

    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        let newPromiseAsyncFunc = promiseAsyncFunc()
        let fulfillFunc = function(value) {
          newPromiseAsyncFunc.fulfill(_fulfillCallback(value))
        }
        let rejectFunc = function(err) {
          newPromiseAsyncFunc.fulfill(_rejectCallback(err))
        }
        if (status === PENDING) {
          fulfillCallback = fulfillFunc
          rejectCallback = rejectFunc
        } else {
          value.then(fulfillFunc, rejectFunc)
        }
        return newPromiseAsyncFunc.promise
      }
    }

如此,我们变得到了一个可以链式调用的 promise。让我们来测试一下:

const PENDING = Symbol("pending")
const FULFILLED = Symbol("fulfilled")
const REJECTED = Symbol("rejected")

function makeThenable(value, status) {
  if (value && typeof value.then === "function") {
    return value
  }
  if (status === FULFILLED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(fulfillCallback(value), FULFILLED)
      }
    }
  }
  if (status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}

// Promise 形式的异步方法定义
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = makeThenable(_value, FULFILLED)
      status = FULFILLED
      if (typeof fulfillCallback === "function") {
        value.then(fulfillCallback)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = makeThenable(error, REJECTED)
      status = REJECTED
      if (typeof rejectCallback === "function") {
        value.then(null, rejectCallback)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        let newPromiseAsyncFunc = promiseAsyncFunc()
        let fulfillFunc = function(value) {
          newPromiseAsyncFunc.fulfill(_fulfillCallback(value))
        }
        let rejectFunc = function(err) {
          newPromiseAsyncFunc.fulfill(_rejectCallback(err))
        }
        if (status === PENDING) {
          fulfillCallback = fulfillFunc
          rejectCallback = rejectFunc
        } else {
          value.then(fulfillFunc, rejectFunc)
        }
        return newPromiseAsyncFunc.promise
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  asyncCall(defer.fulfill, defer.reject)
  return defer.promise
}

let testChainedPromise = ownPromise(function(fulfill, reject) {
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
})
  .then(
    data => {
      console.log(data)
      return "return value in then1 fulfill"
    },
    err => {
      console.log(err)
      return "return value in then1 reject"
    }
  )
  .then(
    data => {
      console.log(data)
      return "return value in then2 fulfill"
    },
    err => {
      console.log(err)
      return "return value in then2 reject"
    }
  )
  .then(
    data => {
      console.log(data)
    },
    err => {
      console.log(err)
    }
  )

/**
console output:

0.9931984611850693
return value in then1 fulfill
return value in then2 fulfill
*/
第六版 Promise: Error handling
这里我们只对异步调用fulfill 回调中抛出的 error 进行处理。

首先是异步调用部分,我们将其 try catch 起来,在发生异常时调用 reject 方法,并将异常作为参数传入。

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  try {
    asyncCall(defer.fulfill, defer.reject)
  } catch (e) {
    defer.reject(e)
  }
  return defer.promise
}

然后是 fulfill 中可能出现的异常。我们对fulfillCallback(value)可能出现的异常进行捕获,并将异常传递给rejectCallback

function makeThenable(value, status) {
  if (value && typeof value.then === "function") {
    return value
  }
  if (status === FULFILLED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        try {
          let newValue = fulfillCallback(value)
          return makeThenable(newValue, FULFILLED)
        } catch (e) {
          return makeThenable(rejectCallback(e), FULFILLED)
        }
      }
    }
  }
  if (status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}

最后让我们对完整的代码进行测试:

const PENDING = Symbol("pending")
const FULFILLED = Symbol("fulfilled")
const REJECTED = Symbol("rejected")

function makeThenable(value, status) {
  if (value && typeof value.then === "function") {
    return value
  }
  if (status === FULFILLED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        try {
          let newValue = fulfillCallback(value)
          return makeThenable(newValue, FULFILLED)
        } catch (e) {
          return makeThenable(rejectCallback(e), FULFILLED)
        }
      }
    }
  }
  if (status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}

// Promise 形式的异步方法定义
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = makeThenable(_value, FULFILLED)
      status = FULFILLED
      if (typeof fulfillCallback === "function") {
        value.then(fulfillCallback)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = makeThenable(error, REJECTED)
      if (typeof rejectCallback === "function") {
        value.then(null, rejectCallback)
      }
      status = REJECTED
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        let newPromiseAsyncFunc = promiseAsyncFunc()
        let fulfillFunc = function(value) {
          newPromiseAsyncFunc.fulfill(_fulfillCallback(value))
        }
        let rejectFunc = function(err) {
          newPromiseAsyncFunc.fulfill(_rejectCallback(err))
        }
        if (status === PENDING) {
          fulfillCallback = fulfillFunc
          rejectCallback = rejectFunc
        } else {
          value.then(fulfillFunc, rejectFunc)
        }
        return newPromiseAsyncFunc.promise
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  try {
    asyncCall(defer.fulfill, defer.reject)
  } catch (e) {
    defer.reject(e)
  }
  return defer.promise
}

let testChainedPromise = ownPromise(function(fulfill, reject) {
  throw Error("here is an error in asyncCall")
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
})
  .then(
    data => {
      console.log(data)
      return "return value in then1 fulfill"
    },
    err => {
      console.log(err.message)
      return "return value in then1 reject"
    }
  )
  .then(
    data => {
      console.log(data)
      throw Error("here is an error in fulfill1")
      return "return value in then2 fulfill"
    },
    err => {
      console.log(err.message)
      return "return value in then2 reject"
    }
  )
  .then(
    data => {
      console.log(data)
    },
    err => {
      console.log(err.message)
    }
  )


// console out:
Error: here is an error in asyncCall
return value in then1 reject
Error: here is an error in fulfill1
return value in then2 reject
总结

以上就是我们对于 Promise 的一个简单的实现,实现思路主要参考了 Q-A promise library for javascript。该实现的 Promise 功能较为简陋,仅实现了部分 api/规范。有任何意见和建议欢迎在评论区交流 ;)

进一步阅读 && 引用

对于 Promise 使用以及error handle 的讲解:

https://medium.com/javascript...

Promise 实现库之一: Q 对于 Promise 实现的讲解:

https://github.com/kriskowal/...
想了解更多 前端 和 数据可视化 ?

这里是我的 前端、_D3.js_ 、 数据可视化 的 github 地址, 欢迎 star & fork

ssthouse-blog

如果觉得本文不错的话, 不妨点击下面的链接关注一下 : )

github 主页

知乎专栏

掘金

想直接联系我 ?

邮箱: ssthouse@163.com

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99993.html

相关文章

  • 2017-10-27 前端日报

    摘要:前端日报精选装饰器场景实战配置之后端渲染理解同步异步和事件循环编写高性能注意点线性渐变实现虚线等简单实用图形中文服务端渲染开发指南个人文章系列之事件类型个人文章使用必记掘金简介掘金性能大乱斗前端杂谈中简单的数据图形化 2017-10-27 前端日报 精选 JS 装饰器(Decorator)场景实战webpack配置之后端渲染JavaScript:理解同步、异步和事件循环编写高性能js注...

    Jeffrrey 评论0 收藏0
  • 杂谈前端Web通信

    摘要:作为开发同学的小伙伴客户端的浏览器,有点小调皮还做了一个同源策略的限制,当我们的数据请求遇到不同源的情况下跨域,我们就得尝试其它的通信方法,不能一条道走到黑。 showImg(https://segmentfault.com/img/bVburZO?w=600&h=450); Web2.0以来,Ajax的出世,解决了传统表单提交页面跳转,闪烁白屏等问题。使得Web页面可以实现局部更新,...

    Betta 评论0 收藏0
  • 前端杂谈: CSS 权重 (Specificity)

    摘要:前端杂谈权重权重想必大家都听说过一些简单的规则大部分人也都知道较长的权重会大于较短的权重高于但是具体规范是什么浏览器是按照什么标准来判定不同选择器的权重的呢让我们来看一下官方文档是怎么说的第一个关键词官方文档中用特异性来表示一个和其元素的相 前端杂谈: CSS 权重 (Specificity) css 权重想必大家都听说过, 一些简单的规则大部分人也都知道: 较长的 css sele...

    SimonMa 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<