Promise机制

Javascript 采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的回调金字塔(Pyramid of Doom),绝对是一种糟糕的编程体验。于是便有了 CommonJS 的 Promises/A 规范,用于解决回调金字塔问题。

回调金字塔

那么何为回调金字塔呢?简单的讲就是回调里面嵌套回调。例如:

asyncOperation(function(data){
  // 处理 `data`
  anotherAsync(function(data2){
      // 处理 `data2`
      yetAnotherAsync(function(){
          // 完成
      });
  });
});

Promise简介

那么什么是Promise机制呢?Promise代表一个目前还不可用,但是在未来的某个时间点可以被解析的值,它允许以一种同步的方式来编写异步代码。例如,使用Promise API执行异步调用远程服务,但是在发起请求前你并不知道返回的数据对象是什么样子,你可以创建一个Promise对象作为未来某个时间返回的数据对象,在此期间, Promise对象扮演了真实数据的代理角色。 例如,使用Promise进行网络请求。

this.AsyncFunction(para).then(
            (para)=>{
             // 处理成功的事件
            }
        ).catch(
            (error)=>{
             // 处理失败的事件
            } )

Promises 将嵌套的 callback ,改造成一系列的.then的连缀调用,去除了层层缩进的糟糕代码风格。 Promises 不是一种解决具体问题的算法,而已一种更好的代码组织模式。接受新的组织模式同时,也逐渐以全新的视角来理解异步调用。

Then 方法

一个 promise 必须提供一个 then 方法以访问其当前值、终值和据因。promise 的 then 方法接受两个参数:

promise.then(onFulfilled, onRejected)

onFulfilled 和 onRejected 都是可选参数。如果 onFulfilled和onRejected 不是函数,其必须被忽略。onFulfilled和onRejected必须在执行后才能被调用,并且只能调用一次。

then 方法可以被同一个 promise 调用多次。当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调;当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调。

then 方法必须返回一个 promise 对象 。即:

promise2 = promise1.then(onFulfilled, onRejected);   

如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)。

Promise 解决过程

Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。

这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。

运行 [[Resolve]](promise, x) 需遵循以下步骤:

x 与 promise 相等

如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise

x 为 Promise

如果 x 为 Promise ,则使 promise 接受 x 的状态 :

  • 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
  • 如果 x 处于执行态,用相同的值执行 promise
  • 如果 x 处于拒绝态,用相同的据因拒绝 promise x 为对象或函数

如果 x 为对象或者函数:

把 x.then 赋值给 then

  • 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
  • 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
  • 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
  • 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
  • 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用 如果调用 then 方法抛出了异常 e:
  • 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之 否则以 e 为据因拒绝 promise
  • 如果 then 不是函数,以 x 为参数执行 promise
  • 如果 x 不为对象或者函数,以 x 为参数执行 promise

如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise

Promises规范

到目前为止Promises指定了A、B、D、A+…版本。

Promises /A

promise 表示一个最终值,该值由操作完成时返回。 promise 有三种状态:未完成 (unfulfilled),完成 (fulfilled) 和失败 (failed)。 promise 的状态只能由未完成转换成完成,或者未完成转换成失败 。 promise 的状态转换只发生一次。 promise 有一个 then 方法, then 方法可以接受 3 个函数作为参数。前两个函数对应 promise 的两种状态 fulfilled 和 rejected 的回调函数。第三个函数用于处理进度信息(对进度回调的支持是可选的)。

promiseSomething().then(function(fulfilled){
        //当 promise 状态变成 fulfilled 时,调用此函数
    },function(rejected){
        //当 promise 状态变成 rejected 时,调用此函数
    },function(progress){
        //当返回进度信息时,调用此函数
    });

Promises/B

在 Promises/A 的基础上, Promises/B 定义了一组 promise 模块需要实现的 API:

when(value, callback, errback_opt)

如果 value 不是一个 promise ,那么下一事件循环 callback 会被调用, value 作为 callback 的传入值。如果 value 是一个 promise , promise 的状态已经完成或者变成完成时,那么下一事件循环 callback 会被调用, resolve 的值会被传入 callback ; promise 的状态已经失败或者变成失败时,那么下一事件循环 errback 会被调用, reason 会作为失败的理由传入 errback 。

asap(value, callback, errback_opt)

与 when 最大的区别,如果 value 不是一个 promise ,会被立即执行,不会等到下一事件循环。

enqueue(task Function)

尽可能快地在接下来的事件循环调用 task 方法。

get(object, name)

返回一个获得对象属性的 promise 。

post(object, name, args)

返回一个调用对象方法的 promise 。

put(object, name, value)

返回一个修改对象属性的 promise 。

del(object, name)

返回一个删除对象属性的 promise 。

makePromise(descriptor Object, fallback Function)

返回一个 promise 对象,该对象必须是一个可调用的函数,也可能是可被实例化的构造函数。

综合示例:

 {
        "when": function(errback){...},
        "get": function(name){...},
        "put": function(name, value){...},
        "post": function(name, args){...},
        "del": function(name){...},
    }
defer()

返回一个对象,该对象包含一个 resolve(value) 方法和一个 promise 属性。 当 resolve(value) 方法被第一次调用时, promise 属性的状态变成 完成,所有之前或之后观察该 promise 的 promise 的状态都被转变成 完成。 value 参数如果不是一个 promise ,会被包装成一个 promise 的 ref 。 resolve 方法会忽略之后的所有调用。

reject(reason String)

返回一个被标记为 失败 的 promise 。 一个失败的 promise 上被调用 when(message) 方法时,会采用如下两种方法之一

  1. 如果存在 errback , errback 会以 reason 作为参数被调用。 when 方法会将 errback 的返回值返回。
  2. 如果不存在 errback , when 方法返回一个新的 reject 状态的 promise 对象,以同一 reason 作为参数。
ref(value)

如果 value 是 promise 对象,返回 value 本身。否则,返回一个 resolved 的 promise ,携带如下 handle 。

  1. when(errback),忽略 errback ,返回 resolved 值
  2. get(name),返回 resolved 值的对应属性。
  3. put(name, value) ,设置 resolved 值的对应属性。
  4. del(name),删除 resolved 值的对应属性。
  5. post(name, args), 调用 resolved 值的对应方法。
  6. 其他所有的调用都返回一个 reject ,并携带 “Promise does not handle NAME” 的理由。
isPromise(value) Boolean

判断一个对象是否是 promise。

method(name String)

获得一个返回 name 对应方法的 promise 。返回值是 “get”, “put”, “del” 和 “post” 对应的方法,但是会在下一事件循环返回。

Promises/D

为了增加不同 promise 实现之间的可互操作性, Promises/D 规范对 promise 对象和 Promises/B 规范做了进一步的约定。以达到鸭子类型的效果( Duck-type Promise )。简单来说 Promises/D 规范,做了两件事情:

  1. 如何判断一个对象是 Promise 类型。
  2. 对 Promises/B 规范进行细节补充。

判断Promise 对象

Promise 对象必须是实现 promiseSend 方法。

  1. 在 promise 库上下文中,如果对象包含 promiseSend 方法就可以甄别为 promise 对象
  2. promiseSend 方法必须接受一个操作名称,作为第一个参数 操作名称是一个可扩展的集合,下面是一些保留名称: when,此时第三个参数必须是 rejection 回调。 rejection 回调必须接受一个 rejection 原因(可以是任何值)作为第一个参数 get,此时第三个参数为属性名(字符串类型) put,此时第三个参数为属性名(字符串类型),第四个参数为新属性值。 del,此时第三个参数为属性名 post,此时第三个参数为方法的属性名,接下来的变参为方法的调用参数 isDef
  3. promiseSend方法的第二个参数为 resolver 方法
  4. promiseSend方法可能接受变参
  5. promiseSend方法必须返回undefined

Promises/A+

前面提到的 Promises/A/B/D 规范都是有 CommonJS 组织提出的, Promises/A+是有一个自称为Promises/A+ 组织发布的,该规范是以 Promises/A 作为基础进行补充和修订,旨在提高 promise 实现之间的可互操作性。

Promises/A+ 对.then方法进行细致的补充,定义了细致的Promise Resolution Procedure流程,并且将.then方法作为 promise 的对象甄别方法。此外, Promises/A+ 还提供了兼容性测试工具,以确定各个实现的兼容性。

Promise判断

状态机

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {
  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value or error once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers attached by calling .then or .done
  var handlers = [];
}

状态变迁 仅支持两种状态变迁, fulfill 和 reject。

function Promise() {
    // ...

  function fulfill(result) {
    state = FULFILLED;
    value = result;
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }

}

fulfill 和 reject 方法较为底层,通常更高级的 resolve 方法开放给外部。

function Promise() {

  // ...

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      fulfill(result);
    } catch (e) {
      reject(e);
    }
  }
}

resolve 方法可以接受一个普通值或者另一个 promise 作为参数,如果接受一个 promise 作为参数,等待其完成。 promise 不允许被另一个 promise fulfill ,所以需要开放 resolve 方法。 resolve 方法依赖一些帮助方法定义如下:

function getThen(value) {
  var t = typeof value;
  if (value && (t === 'object' || t === 'function')) {
    var then = value.then;
    if (typeof then === 'function') {
      return then;
    }
  }
  return null;
}


function doResolve(fn, onFulfilled, onRejected) {
  var done = false;
  try {
    fn(function (value) {
      if (done) return
      done = true
      onFulfilled(value)
    }, function (reason) {
      if (done) return
      done = true
      onRejected(reason)
    })
  } catch (ex) {
    if (done) return
    done = true
    onRejected(ex)
  }
}

这里 resolve 和 doResolve 之间的递归很巧妙,用来处理 promise 的层层嵌套( promise 的 value 是一个 promise )。

构造器

function Promise(fn) {
    // ...
    doResolve(fn, resolve, reject);
}

.done 方法

function Promise(fn) {
  // ...

  function handle(handler) {
    if (state === PENDING) {
      handlers.push(handler);
    } else {
      if (state === FULFILLED &&
        typeof handler.onFulfilled === 'function') {
        handler.onFulfilled(value);
      }
      if (state === REJECTED &&
        typeof handler.onRejected === 'function') {
        handler.onRejected(value);
      }
    }
  }

  this.done = function (onFulfilled, onRejected) {
    // ensure we are always asynchronous
    setTimeout(function () {
      handle({
        onFulfilled: onFulfilled,
        onRejected: onRejected
      });
    }, 0);
  }
  // ...
}

.then 方法

function Promise(fn) {
    // ...
    this.then = function (onFulfilled, onRejected) {
      var self = this;
      return new Promise(function (resolve, reject) {
        return self.done(function (result) {
          if (typeof onFulfilled === 'function') {
            try {
              return resolve(onFulfilled(result));
            } catch (ex) {
              return reject(ex);
            }
          } else {
            return resolve(result);
          }
        }, function (error) {
          if (typeof onRejected === 'function') {
            try {
              return resolve(onRejected(error));
            } catch (ex) {
              return reject(ex);
            }
          } else {
            return reject(error);
          }
        });
      });
    }
    // ...
}

小结

jQuery 1.8 之前的版本, jQuery 的 then 方法只是一种可以同时调用 done 、 fail 和 progress 这三种回调的速写方法,而 Promises/A 规范的 then 在行为上更像是 jQuery 的 pipe 。 jQuery 1.8 修正了这个问题,使 then 成为 pipe 的同义词。不过,由于向后兼容的问题, jQuery 的 Promise 再如何对 Promises/A 示好也不太会招人待见。

此外,在 Promises/A 规范中,由 then 方法生成的 Promise 对象是已执行还是已拒绝,取决于由 then 方法调用的那个回调是返回值还是抛出错误。在 JQuery 的 Promise 对象的回调中抛出错误是个糟糕的主意,因为错误不会被捕获。 最后一个例子揭示了,实现 Promise 的关键是实现好 doResolve 方法,在完事以后触发回调。而为了保证异步 setTimeout(fun, 0); 是关键一步。

附:Promise

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏AzMark

Python 学习笔记之类与实例

类 (class) 封装一组相关数据,使之成为一个整体,并使用一种方法持续展示和维护。

5610
来自专栏测试开发架构之路

C语言之预处理命令与用typedef命名已有类型

 预处理命令 主要是改进程序设计环境,以提高编程效率,不属于c语言本身的组成部分,不能直接对它们进行编译,必须在对 程序编译之前,先对程序中的这些特殊命令进行...

42890
来自专栏xingoo, 一个梦想做发明家的程序员

第三章 C++中的C ----《C++编程思想》

1 创建函数 2 执行控制语句   break:退出循环,不再执行循环中的生育语句   continue:停止执行当前的循环,返回到循环的起始处开始新的一轮循环...

17970
来自专栏PHP实战技术

你应该这个姿势学习PHP(1)

  应用场景:能防止sql的注入(当然并不完全是可以,我们可以使用pdo进行预处理然后方式sql的注入,安全不能只靠一种方式防止事情的发生)

538170
来自专栏用户2442861的专栏

Java中Synchronized的用法

原文:http://blog.csdn.net/luoweifu/article/details/46613015 作者:luoweifu 转载请标名...

8810
来自专栏magicsoar

C++获取private的变量-偷走private

private提供了对数据的封装,使得private成员只能被类自身的成员函数以及类的友元访问,其他的函数或者类想要访问private成员只能通过该类所提供的s...

191100
来自专栏互联网杂技

前端--理解 Promise 的工作原理

Javascript 采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的...

38760
来自专栏LanceToBigData

JavaSE(十一)之异常处理详解

一、异常概述   在我们日常生活中,有时会出现各种各样的异常,例如:职工小王开车去上班,在正常情况下,小王会准时到达单位。但是天有不测风云,在小王去上班时,可能...

20790
来自专栏专注 Java 基础分享

Java 的异常处理机制

13920
来自专栏C/C++基础

C++中的作用域与生命周期

Pascal之父Nicklaus Wirth曾经提出一个公式,展示出了程序的本质:程序=算法+数据结构。后人又给出一个公式与之遥相呼应:软件=程序+文档。这两个...

9020

扫码关注云+社区

领取腾讯云代金券