[跳跳的面试系列] Promise A+规范

1,152 阅读8分钟

Promise A+ 学习

在js的本身被设计为异步的,那么这就给编码提出了一个要求,该如何去优雅的编写js代码中异步的代码,最早的时候,我们使用回调来处理这个问题。但是同时也产生了回调地狱的问题。

后来则出现了Promise,Promise的出现可以说彻底改变了对于异步代码的编写方式。Promise的影响之深远,可以说es6加入了Promise,koa中yield,Generator也是配合了Promise才实现了类似await,async的功能。知道ES7中真正的async和await也是基于Promise实现的(其实async和await只是Generator的语法糖)。可以说Promise在异步编程中已经无处不在了。

所以了解Promise的规范还是很有必要的。当然最好的是去看一下规范的原文。

Promise A+的英文原文:Promise/A+

下面对于原文的翻译,我将会引用一个网上已有的翻译版本【翻译】Promises/A+规范的内容,在此感激作者。

一个开放、健全且通用的 JavaScript Promise 标准。由开发者制定,供开发者参考。

Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是 then 方法,该方法注册了两个回调函数,用于接收 promise 的终值或本 promise 不能执行的原因。

本规范详细列出了 then 方法的执行过程,所有遵循 Promises/A+ 规范实现的 promise 均可以本标准作为参照基础来实施 then 方法。因而本规范是十分稳定的。尽管 Promise/A+ 组织有时可能会修订本规范,但主要是为了处理一些特殊的边界情况,且这些改动都是微小且向下兼容的。如果我们要进行大规模不兼容的更新,我们一定会在事先进行谨慎地考虑、详尽的探讨和严格的测试。

从历史上说,本规范实际上是把之前 Promise/A 规范 中的建议明确成为了行为标准:我们一方面扩展了原有规范约定俗成的行为,一方面删减了原规范的一些特例情况和有问题的部分。

最后,核心的 Promises/A+ 规范不设计如何创建、解决和拒绝 promise,而是专注于提供一个通用的 then 方法。上述对于 promises 的操作方法将来在其他规范中可能会提及。

术语

1.1 Promise

promise 是一个拥有 then 方法的对象或函数,其行为符合本规范;

1.2 thenable

是一个定义了 then 方法的对象或函数,文中译作“拥有 then 方法”;

1.3 值(value)

指任何 JavaScript 的合法值(包括 undefined , thenablepromise);

1.4 异常(exception)

是使用 throw 语句抛出的一个值。

1.5 据因(reason)

表示一个 promise 的拒绝原因。

要求

2.1 Promise 的状态

一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。

2.1.1 等待态(Pending)

处于等待态时,promise 需满足以下条件:

  • 可以迁移至执行态或拒绝态

2.1.2 执行态(Fulfilled)

处于执行态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的终值

2.1.3 拒绝态(Rejected)

处于拒绝态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的据因

这里的不可变指的是恒等(即可用 === 判断相等),而不是意味着更深层次的不可变

2.2 then方法

一个 promise 必须提供一个 then 方法以访问其当前值、终值和据因

promisethen 方法接受两个参数:

promise.then(onFulfilled, onRejected)

2.2.1 onFulfilledonRejected 都是可选参数。

2.2.1.1 如果 onFulfilled 不是函数,其必须被忽略
2.2.1.2 如果 onRejected 不是函数,其必须被忽略

2.2.2 如果 onFulfilled 是函数:

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

类似 onFulfilled(value);

2.2.2.2 在 promise 执行结束前其不可被调用
// 这里的onFulfilled为伪代码,表示onFulfilled的执行函数
var onFulfilled = function() {
  console.log("onFulfilled");
}

var c = new Promise((onFulfilled, onRejected) => {
  onFulfilled('ok');
  console.log('123');
})

> '123'
> 'onFulfilled'

2.2.2.3 其调用次数不可超过一次

var c = new Promise((onFulfilled, onRejected) => {
  onFulfilled('ok');
  onFulfilled('ok2');
});

c.then((data) => {
  console.log(data); // 'ok'
})

2.2.3 如果 onRejected 是函数:

2.2.3.1 当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的据因
2.2.3.2 在 promise 被拒绝执行前其不可被调用

2.2.3.3 其调用次数不可超过一次

2.2.4 onFulfilledonRejected 只有在执行环境堆栈仅包含平台代码时才可被调用

2.2.5 onFulfilled 和 onRejected 必须被作为函数调用(即没有 this 值)

2.2.6 then 方法可以被同一个 promise 调用多次

2.2.6.1 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
2.2.6.1 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调

2.2.7 then 必须返回一个 promise

promise2 = promise1.then(onFulfilled, onRejected);
2.2.7.1 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
2.2.7.2 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
2.2.7.3 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
2.2.7.4 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因

2.3 Promise 解决过程

注释:这里是描述的是Promise.then() 执行以后返回什么内容,其实Promise.then()一定会返回一个新的Promise,但是具体是什么内容,是和then里的回调函数的返回值有关,下面的x就是then的回调函数的返回值。可以再看一眼上面2.2.7的内容

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

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

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

2.3.1 x 与 promise 相等

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

var p = new Promise(function(resolve, reject) {
  resolve(123);
});

/*
 then的回调函数返回了p2, 和promise是同一个变量,那么就会返回一个错误
 */
var p2 = p.then((data) => {
  return p2;
});

>> Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

2.3.2 x 为 Promise

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

2.3.2.1 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝

2.3.2.2 如果 x 处于执行态,用相同的值执行 promise

2.3.2.3 如果 x 处于拒绝态,用相同的据因拒绝 promise

这里其实是我们经常会用到的一个特性,就是promise链式调用,这里就不多解释了

getUserInfo().then((userInfo) => {
  return getCompanyInfo();
}).then((companyInfo) => {
  return getCityInfo();
}).then((cityInfo) => {
})

2.3.3 x 为对象或函数

2.3.3.1 把 x.then 赋值给 then

这里相当于创建了一个新的变量then,类似 var then = x.then;, 作用其实和我们平常的这样做的方式差不多,方便后面的使用。

2.3.3.2 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise

function objFactory() {
  var obj = {};
  Object.defineProperty(obj, "then", {
    get: function() {
      throw Error('then error');
    }
  });
  return obj;
}

var a = new Promise((resolve) => {
  resolve();
});

a.then(() => {
  return objFactory()
});
// Uncaught (in promise) Error: then error

2.3.3.3 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:

// 类似这个代码
var then = x.then;

if (typeof then === 'function') {
  then.call(x, resolvePromise, rejectPromise);
}

2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)

function objFactory() {
  var obj = {};
  Object.defineProperty(obj, "then", {
    get: function() {
      return function(resolvePromise, rejectPromise) {
        resolvePromise('resolve_y');
      }
    }
  });
  return obj;
}

var a = new Promise((resolve) => {
  resolve();
});

var c = a.then(() => {
  return objFactory()
});

c.then((data) => {
  console.log(data); // "resolve_y"
})
2.3.3.3.2 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
// 类似 2.3.3.3.1
function objFactory() {
  var obj = {};
  Object.defineProperty(obj, "then", {
    get: function() {
      return function(resolvePromise, rejectPromise) {
        rejectPromise('resolve_y');
      }
    }
  });
  return obj;
}
2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用

这个就不给例子了,表述也很直接了

2.3.3.3.4 如果调用 then 方法抛出了异常 e:
2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
function objFactory() {
  var obj = {};
  Object.defineProperty(obj, "then", {
    get: function() {
      return function(resolvePromise, rejectPromise) {
        resolvePromise('resolve_y');
        console.log(test);
      }
    }
  });
  return obj;
}

var a = new Promise((resolve) => {
  resolve();
});

var c = a.then(() => {
  return objFactory()
});

c.then((data) => {
  console.log(data); // "resolve_y"
})
2.3.3.3.4.2 否则以 e 为据因拒绝 promise
function objFactory() {
  var obj = {};
  Object.defineProperty(obj, "then", {
    get: function() {
      return function(resolvePromise, rejectPromise) {
        console.log(test);
        resolvePromise('resolve_y');
      }
    }
  });
  return obj;
}

var a = new Promise((resolve) => {
  resolve();
});

var c = a.then(() => {
  return objFactory()
});

// VM1124:6 Uncaught (in promise) ReferenceError: test is not defined
2.3.3.3.5 如果 then 不是函数,以 x 为参数执行 promise
function objFactory() {
  var obj = {};
  Object.defineProperty(obj, "then", {
    get: function() {
      return 'test';
    }
  });
  return obj;
}

var a = new Promise((resolve) => {
  resolve();
});

var c = a.then(() => {
  return objFactory()
});

c.then((data) => {
  console.log(data); // object {then: "test"}
})

2.3.3.4 如果 x 不为对象或者函数,以 x 为参数执行 promise

var a = new Promise((resolve) => {
  resolve();
});

var c = a.then(() => {
  return "test"
});

c.then((data) => {
  console.log(data); // "test"
})

未完待续