promises的深入学习

Promise 的含义 § 

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise对象有以下两个特点。

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

注意,为了行文方便,本章后面的resolved统一只指fulfilled状态,不包含rejected状态。

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

如果某些事件不断地反复发生,一般来说,使用 Stream 模式是比部署Promise更好的选择。

基本用法

ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

下面代码创造了一个Promise实例。

1 const promise = new Promise(function(resolve, reject) {
2   // ... some code
3 
4   if (/* 异步操作成功 */){
5     resolve(value);
6   } else {
7     reject(error);
8   }
9 });

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

 

Promise 代码实现

  1 /**
  2  * Promise 实现 遵循promise/A+规范
  3  * Promise/A+规范译文:
  4  * https://malcolmyu.github.io/2015/06/12/Promises-A-Plus/#note-4
  5  */
  6 
  7 // promise 三个状态
  8 const PENDING = "pending";
  9 const FULFILLED = "fulfilled";
 10 const REJECTED = "rejected";
 11 
 12 function Promise(excutor) {
 13     let that = this; // 缓存当前promise实例对象
 14     that.status = PENDING; // 初始状态
 15     that.value = undefined; // fulfilled状态时 返回的信息
 16     that.reason = undefined; // rejected状态时 拒绝的原因
 17     that.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数
 18     that.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数
 19 
 20     function resolve(value) { // value成功态时接收的终值
 21         if(value instanceof Promise) {
 22             return value.then(resolve, reject);
 23         }
 24 
 25         // 为什么resolve 加setTimeout?
 26         // 2.2.4规范 onFulfilled 和 onRejected 只允许在 execution context 栈仅包含平台代码时运行.
 27         // 注1 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
 28 
 29         setTimeout(() => {
 30             // 调用resolve 回调对应onFulfilled函数
 31             if (that.status === PENDING) {
 32                 // 只能由pedning状态 => fulfilled状态 (避免调用多次resolve reject)
 33                 that.status = FULFILLED;
 34                 that.value = value;
 35                 that.onFulfilledCallbacks.forEach(cb => cb(that.value));
 36             }
 37         });
 38     }
 39 
 40     function reject(reason) { // reason失败态时接收的拒因
 41         setTimeout(() => {
 42             // 调用reject 回调对应onRejected函数
 43             if (that.status === PENDING) {
 44                 // 只能由pedning状态 => rejected状态 (避免调用多次resolve reject)
 45                 that.status = REJECTED;
 46                 that.reason = reason;
 47                 that.onRejectedCallbacks.forEach(cb => cb(that.reason));
 48             }
 49         });
 50     }
 51 
 52     // 捕获在excutor执行器中抛出的异常
 53     // new Promise((resolve, reject) => {
 54     //     throw new Error('error in excutor')
 55     // })
 56     try {
 57         excutor(resolve, reject);
 58     } catch (e) {
 59         reject(e);
 60     }
 61 }
 62 
 63 /**
 64  * resolve中的值几种情况:
 65  * 1.普通值
 66  * 2.promise对象
 67  * 3.thenable对象/函数
 68  */
 69 
 70 /**
 71  * 对resolve 进行改造增强 针对resolve中不同值情况 进行处理
 72  * @param  {promise} promise2 promise1.then方法返回的新的promise对象
 73  * @param  {[type]} x         promise1中onFulfilled的返回值
 74  * @param  {[type]} resolve   promise2的resolve方法
 75  * @param  {[type]} reject    promise2的reject方法
 76  */
 77 function resolvePromise(promise2, x, resolve, reject) {
 78     if (promise2 === x) {  // 如果从onFulfilled中返回的x 就是promise2 就会导致循环引用报错
 79         return reject(new TypeError('循环引用'));
 80     }
 81 
 82     let called = false; // 避免多次调用
 83     // 如果x是一个promise对象 (该判断和下面 判断是不是thenable对象重复 所以可有可无)
 84     if (x instanceof Promise) { // 获得它的终值 继续resolve
 85         if (x.status === PENDING) { // 如果为等待态需等待直至 x 被执行或拒绝 并解析y值
 86             x.then(y => {
 87                 resolvePromise(promise2, y, resolve, reject);
 88             }, reason => {
 89                 reject(reason);
 90             });
 91         } else { // 如果 x 已经处于执行态/拒绝态(值已经被解析为普通值),用相同的值执行传递下去 promise
 92             x.then(resolve, reject);
 93         }
 94         // 如果 x 为对象或者函数
 95     } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
 96         try { // 是否是thenable对象(具有then方法的对象/函数)
 97             let then = x.then;
 98             if (typeof then === 'function') {
 99                 then.call(x, y => {
100                     if(called) return;
101                     called = true;
102                     resolvePromise(promise2, y, resolve, reject);
103                 }, reason => {
104                     if(called) return;
105                     called = true;
106                     reject(reason);
107                 })
108             } else { // 说明是一个普通对象/函数
109                 resolve(x);
110             }
111         } catch(e) {
112             if(called) return;
113             called = true;
114             reject(e);
115         }
116     } else {
117         resolve(x);
118     }
119 }
120 
121 /**
122  * [注册fulfilled状态/rejected状态对应的回调函数]
123  * @param  {function} onFulfilled fulfilled状态时 执行的函数
124  * @param  {function} onRejected  rejected状态时 执行的函数
125  * @return {function} newPromsie  返回一个新的promise对象
126  */
127 Promise.prototype.then = function(onFulfilled, onRejected) {
128     const that = this;
129     let newPromise;
130     // 处理参数默认值 保证参数后续能够继续执行
131     onFulfilled =
132         typeof onFulfilled === "function" ? onFulfilled : value => value;
133     onRejected =
134         typeof onRejected === "function" ? onRejected : reason => {
135             throw reason;
136         };
137 
138     // then里面的FULFILLED/REJECTED状态时 为什么要加setTimeout ?
139     // 原因:
140     // 其一 2.2.4规范 要确保 onFulfilled 和 onRejected 方法异步执行(且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行) 所以要在resolve里加上setTimeout
141     // 其二 2.2.6规范 对于一个promise,它的then方法可以调用多次.(当在其他程序中多次调用同一个promise的then时 由于之前状态已经为FULFILLED/REJECTED状态,则会走的下面逻辑),所以要确保为FULFILLED/REJECTED状态后 也要异步执行onFulfilled/onRejected
142 
143     // 其二 2.2.6规范 也是resolve函数里加setTimeout的原因
144     // 总之都是 让then方法异步执行 也就是确保onFulfilled/onRejected异步执行
145 
146     // 如下面这种情景 多次调用p1.then
147     // p1.then((value) => { // 此时p1.status 由pedding状态 => fulfilled状态
148     //     console.log(value); // resolve
149     //     // console.log(p1.status); // fulfilled
150     //     p1.then(value => { // 再次p1.then 这时已经为fulfilled状态 走的是fulfilled状态判断里的逻辑 所以我们也要确保判断里面onFuilled异步执行
151     //         console.log(value); // 'resolve'
152     //     });
153     //     console.log('当前执行栈中同步代码');
154     // })
155     // console.log('全局执行栈中同步代码');
156     //
157 
158     if (that.status === FULFILLED) { // 成功态
159         return newPromise = new Promise((resolve, reject) => {
160             setTimeout(() => {
161                 try{
162                     let x = onFulfilled(that.value);
163                     resolvePromise(newPromise, x, resolve, reject); // 新的promise resolve 上一个onFulfilled的返回值
164                 } catch(e) {
165                     reject(e); // 捕获前面onFulfilled中抛出的异常 then(onFulfilled, onRejected);
166                 }
167             });
168         })
169     }
170 
171     if (that.status === REJECTED) { // 失败态
172         return newPromise = new Promise((resolve, reject) => {
173             setTimeout(() => {
174                 try {
175                     let x = onRejected(that.reason);
176                     resolvePromise(newPromise, x, resolve, reject);
177                 } catch(e) {
178                     reject(e);
179                 }
180             });
181         });
182     }
183 
184     if (that.status === PENDING) { // 等待态
185         // 当异步调用resolve/rejected时 将onFulfilled/onRejected收集暂存到集合中
186         return newPromise = new Promise((resolve, reject) => {
187             that.onFulfilledCallbacks.push((value) => {
188                 try {
189                     let x = onFulfilled(value);
190                     resolvePromise(newPromise, x, resolve, reject);
191                 } catch(e) {
192                     reject(e);
193                 }
194             });
195             that.onRejectedCallbacks.push((reason) => {
196                 try {
197                     let x = onRejected(reason);
198                     resolvePromise(newPromise, x, resolve, reject);
199                 } catch(e) {
200                     reject(e);
201                 }
202             });
203         });
204     }
205 };
206 
207 /**
208  * Promise.all Promise进行并行处理
209  * 参数: promise对象组成的数组作为参数
210  * 返回值: 返回一个Promise实例
211  * 当这个数组里的所有promise对象全部变为resolve状态的时候,才会resolve。
212  */
213 Promise.all = function(promises) {
214     return new Promise((resolve, reject) => {
215         let done = gen(promises.length, resolve);
216         promises.forEach((promise, index) => {
217             promise.then((value) => {
218                 done(index, value)
219             }, reject)
220         })
221     })
222 }
223 
224 function gen(length, resolve) {
225     let count = 0;
226     let values = [];
227     return function(i, value) {
228         values[i] = value;
229         if (++count === length) {
230             console.log(values);
231             resolve(values);
232         }
233     }
234 }
235 
236 /**
237  * Promise.race
238  * 参数: 接收 promise对象组成的数组作为参数
239  * 返回值: 返回一个Promise实例
240  * 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理(取决于哪一个更快)
241  */
242 Promise.race = function(promises) {
243     return new Promise((resolve, reject) => {
244         promises.forEach((promise, index) => {
245            promise.then(resolve, reject);
246         });
247     });
248 }
249 
250 // 用于promise方法链时 捕获前面onFulfilled/onRejected抛出的异常
251 Promise.prototype.catch = function(onRejected) {
252     return this.then(null, onRejected);
253 }
254 
255 Promise.resolve = function (value) {
256     return new Promise(resolve => {
257         resolve(value);
258     });
259 }
260 
261 Promise.reject = function (reason) {
262     return new Promise((resolve, reject) => {
263         reject(reason);
264     });
265 }
266 
267 /**
268  * 基于Promise实现Deferred的
269  * Deferred和Promise的关系
270  * - Deferred 拥有 Promise
271  * - Deferred 具备对 Promise的状态进行操作的特权方法(resolve reject)
272  *
273  *参考jQuery.Deferred
274  *url: http://api.jquery.com/category/deferred-object/
275  */
276 Promise.deferred = function() { // 延迟对象
277     let defer = {};
278     defer.promise = new Promise((resolve, reject) => {
279         defer.resolve = resolve;
280         defer.reject = reject;
281     });
282     return defer;
283 }
284 
285 /**
286  * Promise/A+规范测试
287  * npm i -g promises-aplus-tests
288  * promises-aplus-tests Promise.js
289  */
290 
291 try {
292   module.exports = Promise
293 } catch (e) {
294 }

Promises/A+ 

An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.

promise represents the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.

This specification details the behavior of the then method, providing an interoperable base which all Promises/A+ conformant promise implementations can be depended on to provide. As such, the specification should be considered very stable. Although the Promises/A+ organization may occasionally revise this specification with minor backward-compatible changes to address newly-discovered corner cases, we will integrate large or backward-incompatible changes only after careful consideration, discussion, and testing.

Historically, Promises/A+ clarifies the behavioral clauses of the earlier Promises/A proposal, extending it to cover de facto behaviors and omitting parts that are underspecified or problematic.

Finally, the core Promises/A+ specification does not deal with how to create, fulfill, or reject promises, choosing instead to focus on providing an interoperable then method. Future work in companion specifications may touch on these subjects.

Terminology

  1. “promise” is an object or function with a then method whose behavior conforms to this specification.
  2. “thenable” is an object or function that defines a then method.
  3. “value” is any legal JavaScript value (including undefined, a thenable, or a promise).
  4. “exception” is a value that is thrown using the throw statement.
  5. “reason” is a value that indicates why a promise was rejected.

Requirements

Promise States

A promise must be in one of three states: pending, fulfilled, or rejected.

  1. When pending, a promise:
    1. may transition to either the fulfilled or rejected state.
  2. When fulfilled, a promise:
    1. must not transition to any other state.
    2. must have a value, which must not change.
  3. When rejected, a promise:
    1. must not transition to any other state.
    2. must have a reason, which must not change.

Here, “must not change” means immutable identity (i.e. ===), but does not imply deep immutability.

The then Method

A promise must provide a then method to access its current or eventual value or reason.

A promise’s then method accepts two arguments:

promise.then(onFulfilled, onRejected)
  1. Both onFulfilled and onRejected are optional arguments:
    1. If onFulfilled is not a function, it must be ignored.
    2. If onRejected is not a function, it must be ignored.
  2. If onFulfilled is a function:
    1. it must be called after promise is fulfilled, with promise’s value as its first argument.
    2. it must not be called before promise is fulfilled.
    3. it must not be called more than once.
  3. If onRejected is a function,
    1. it must be called after promise is rejected, with promise’s reason as its first argument.
    2. it must not be called before promise is rejected.
    3. it must not be called more than once.
  4. onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].
  5. onFulfilled and onRejected must be called as functions (i.e. with no this value). [3.2]
  6. then may be called multiple times on the same promise.
    1. If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then.
    2. If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then.
  7. then must return a promise [3.3].

     promise2 = promise1.then(onFulfilled, onRejected);
    
    1. If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
    2. If either onFulfilled or onRejected throws an exception epromise2 must be rejected with e as the reason.
    3. If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.
    4. If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.

The Promise Resolution Procedure

The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as [[Resolve]](promise, x). If x is a thenable, it attempts to make promise adopt the state of x, under the assumption that x behaves at least somewhat like a promise. Otherwise, it fulfills promise with the value x.

This treatment of thenables allows promise implementations to interoperate, as long as they expose a Promises/A+-compliant then method. It also allows Promises/A+ implementations to “assimilate” nonconformant implementations with reasonable then methods.

To run [[Resolve]](promise, x), perform the following steps:

  1. If promise and x refer to the same object, reject promise with a TypeError as the reason.
  2. If x is a promise, adopt its state [3.4]:
    1. If x is pending, promise must remain pending until x is fulfilled or rejected.
    2. If/when x is fulfilled, fulfill promise with the same value.
    3. If/when x is rejected, reject promise with the same reason.
  3. Otherwise, if x is an object or function,
    1. Let then be x.then. [3.5]
    2. If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
    3. If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where:
      1. If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
      2. If/when rejectPromise is called with a reason r, reject promise with r.
      3. If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
      4. If calling then throws an exception e,
        1. If resolvePromise or rejectPromise have been called, ignore it.
        2. Otherwise, reject promise with e as the reason.
    4. If then is not a function, fulfill promise with x.
  4. If x is not an object or function, fulfill promise with x.

If a promise is resolved with a thenable that participates in a circular thenable chain, such that the recursive nature of [[Resolve]](promise, thenable) eventually causes [[Resolve]](promise, thenable) to be called again, following the above algorithm will lead to infinite recursion. Implementations are encouraged, but not required, to detect such recursion and reject promise with an informative TypeError as the reason. [3.6]

Notes

  1. Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.

  2. That is, in strict mode this will be undefined inside of them; in sloppy mode, it will be the global object.

  3. Implementations may allow promise2 === promise1, provided the implementation meets all requirements. Each implementation should document whether it can produce promise2 === promise1 and under what conditions.

  4. Generally, it will only be known that x is a true promise if it comes from the current implementation. This clause allows the use of implementation-specific means to adopt the state of known-conformant promises.

  5. This procedure of first storing a reference to x.then, then testing that reference, and then calling that reference, avoids multiple accesses to the x.then property. Such precautions are important for ensuring consistency in the face of an accessor property, whose value could change between retrievals.

  6. Implementations should not set arbitrary limits on the depth of thenable chains, and assume that beyond that arbitrary limit the recursion will be infinite. Only true cycles should lead to a TypeError; if an infinite chain of distinct thenables is encountered, recursing forever is the correct behavior.


promises的深入学习
To the extent possible under law, the Promises/A+ organization has waived all copyright and related or neighboring rights to Promises/A+ Promise Specification. This work is published from: United States.

英文原文:Promise/A+
图灵译文:【翻译】Promises/A+规范

译者序:一年前曾译过 Promise/A+ 规范,适时完全不懂 Promise 的思想,纯粹将翻译的过程当作学习,旧文译下来诘屈聱牙,读起来十分不顺畅。谁知这样一篇拙译,一年之间竟然点击数千,成为谷歌搜索的头条。今日在理解之后重译此规范,以飨读者。

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


译文术语

  • 解决(fulfill):指一个 promise 成功时进行的一系列操作,如状态的改变、回调的执行。虽然规范中用 fulfill 来表示解决,但在后世的 promise 实现多以 resolve 来指代之。
  • 拒绝(reject):指一个 promise 失败时进行的一系列操作。
  • 终值(eventual value):所谓终值,指的是 promise 被解决时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)。
  • 据因(reason):也就是拒绝原因,指在 promise 被拒绝时传递给拒绝回调的值。

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

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

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

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

术语


Promise

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

thenable

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

值(value)

指任何 JavaScript 的合法值(包括 undefined , thenable 和 promise);

异常(exception)

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

据因(reason)

表示一个 promise 的拒绝原因。

要求


Promise 的状态

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

等待态(Pending)

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

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

执行态(Fulfilled)

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

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

拒绝态(Rejected)

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

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

这里的不可变指的是恒等(即可用 === 判断相等),而不是意味着更深层次的不可变(译者注: 盖指当 value 或 reason 不是基本值时,只要求其引用地址相等,但属性值可被更改)。

Then 方法

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

promise 的 then 方法接受两个参数:

promise.then(onFulfilled, onRejected)

参数可选

onFulfilled 和 onRejected 都是可选参数。

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

onFulfilled 特性

如果 onFulfilled 是函数:

  • 当 promise 执行结束后其必须被调用,其第一个参数为 promise 的终值
  • 在 promise 执行结束前其不可被调用
  • 其调用次数不可超过一次

onRejected 特性

如果 onRejected 是函数:

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

调用时机

onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用注1

调用要求

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

多次调用

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

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

返回

then 方法必须返回一个 promise 对象 注3

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

译者注: 理解上面的“返回”部分非常重要,即:不论 promise1 被 reject 还是被 resolve 时 promise2 都会被 resolve,只有出现异常时才会被 rejected。

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 的状态 注4

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

x 为对象或函数

如果 x 为对象或者函数:

  • 把 x.then 赋值给 then 注5
  • 如果取 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 注6

注释


  • 注1 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。这个事件队列可以采用“宏任务(macro-task)”机制或者“微任务(micro-task)”机制来实现。由于 promise 的实施代码本身就是平台代码(译者注: 即都是 JavaScript),故代码自身在处理在处理程序时可能已经包含一个任务调度队列或『跳板』)。

    译者注: 这里提及了 macrotask 和 microtask 两个概念,这表示异步任务的两种分类。在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。

    两个类别的具体分类如下:

    • macro-task: script(整体代码), setTimeoutsetIntervalsetImmediate, I/O, UI rendering
    • micro-task: process.nextTickPromises(这里指浏览器实现的原生 Promise), Object.observeMutationObserver

      详见 * 解答 或 这篇博客

  • 注2 也就是说在 严格模式(strict) 中,函数 this 的值为 undefined ;在非严格模式中其为全局对象。

  • 注3 代码实现在满足所有要求的情况下可以允许 promise2 === promise1 。每个实现都要文档说明其是否允许以及在何种条件下允许 promise2 === promise1 。

  • 注4 总体来说,如果 x 符合当前实现,我们才认为它是真正的 promise 。这一规则允许那些特例实现接受符合已知要求的 Promises 状态。

  • 注5 这步我们先是存储了一个指向 x.then 的引用,然后测试并调用该引用,以避免多次访问 x.then 属性。这种预防措施确保了该属性的一致性,因为其值可能在检索调用时被改变。

  • 注6 实现不应该对 thenable 链的深度设限,并假定超出本限制的递归就是无限循环。只有真正的循环递归才应能导致 TypeError 异常;如果一条无限长的链上 thenable 均不相同,那么递归下去永远是正确的行为。

原:https://www.jianshu.com/p/459a856c476f

上一篇:Promise详解二(解读Promise规范+手写Promise)


下一篇:用户层修改peb实现隐藏一些东西