Promise的机制,实现原理浅析

本文部分转自某博客以及自己的汇总,仅做知识梳理归纳用,不作为个人版权,欢迎批评指正

Promise介绍

Promise对象用于一个异步操作的最终完成(或失败)及其结果值的表示。(一个诺言,一个成功,一个失败。)
Promise 对象用于延迟(deferred) 计算和异步(asynchronous ) 计算。一个Promise对象代表着一个还未完成,但预期将来会完成的操作。 Promise 对象是一个返回值的代理,这个返回值在promise对象创建时未必已知。它允许你为异步操作的成功或失败指定处理方法。 这使得异步方法可以像同步方法那样返回值:异步方法会返回一个包含了原返回值的 promise 对象来替代原返回值。

语法

new Promise(
    /* executor */
    function(resolve, reject) {...}
);

参数:executor

executor是一个带有resolve和reject两个参数的函数。executor 函数在Promise构造函数执行时同步执行,被传递resolve和reject函数(executor 函数在Promise构造函数返回新建对象前被调用)。resolve和reject函数被调用时,分别将promise的状态改为fulfilled(完成)或rejected(失败)。executor 内部通常会执行一些异步操作,一旦完成,可以调用resolve函数来将promise状态改成fulfilled,或者在发生错误时将它的状态改为rejected。

如果在executor函数中抛出一个错误,那么该promise 状态为rejected。executor函数的返回值被忽略。

描述

Promise对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers )。 这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象

一个Promise有以下几种状态:

  • pending: 初始状态,不是成功或失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

pending 状态的 Promise 对象可能触发fulfilled 状态并传递一个值给相应的状态处理方法,也可能触发失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers)就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。

因为Promise.prototype.thenPromise.prototype.catch方法返回promise 对象, 所以它们可以被链式调用。

不要被迷惑了:有一些语言中有惰性求值和延时计算的特性, 它们也被称为"promises" --例如Scheme. Javascript中的promise代表一种已经发生的状态, 而且可以通过回调方法链在一起. 如果你想要的是表达式的延时计算, 考虑无参数的"箭头方法": f = () =>表达式创建惰性求值的表达式,使用f()求值.

注意:如果一个promise对象处在fulfilled或rejected状态而不是pending状态,那么它也可以被称为settled状态。你可能也会听到一个术语resolved,它表示promise对象处于fulfilled状态。(此处有待商榷。按本文的原英文翻译resolved表示fulfilled,但是后面的术语链接又表明resolved和fulfilled并不是一样。)关于promise的术语, Domenic Denicola 的States and fates有更多详情可供参考。

属性

Promise.length

长度属性,其值总是为 1 (构造器参数的数目).

Promise.prototype

表示Promise构造器的原型.

方法

Promise.all(iterable)

这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。(可以参考jQuery.when方法---译者注)

Promise.race(iterable)

当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

Promise.reject(reason)

返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法

Promise.resolve(value)

返回一个状态由给定value决定的Promise对象。如果该值是一个Promise对象,则直接返回该对象;如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。通常而言,如果你不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。

Promise原型

属性

Promise.prototype.constructor

返回创建了实例原型的函数. 默认为Promise函数.

方法

Promise.prototype.catch(onRejected)

添加一个否定(rejection) 回调到当前 promise, 返回一个新的promise。如果这个回调被调用,新 promise 将以它的返回值来resolve,否则如果当前promise 进入fulfilled状态,则以当前promise的肯定结果作为新promise的肯定结果.

Promise.prototype.then(onFulfilled, onRejected)

添加肯定和否定回调到当前 promise, 返回一个新的 promise, 将以回调的返回值 来resolve.

创建Promise

Promise对象是由关键字new及其构造函数来创建的。该构造函数会把一个叫做“处理器函数”(executor function)的函数作为它的参数。这个“处理器函数”接受两个函数——resolvereject——作为其参数。当异步任务顺利完成且返回结果值时,会调用resolve函数;而当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用reject函数。

const myFirstPromise = new Promise((resolve, reject) => {
  // 异步操作,最终调用:
  //
  //   resolve(someValue); // fulfilled
  // 或
  //   reject("failure reason"); // rejected
});

想要某个函数拥有promise功能,只需让其返回一个promise即可。

function myAsyncFunction(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
};

示例

非常简单的例子 (就10行!)

let myFirstPromise = new Promise(function(resolve, reject){
    //当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)
    //在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.
    setTimeout(function(){
        resolve("成功!"); //代码正常执行!
    }, 250);
});

myFirstPromise.then(function(successMessage){
    //successMessage的值是上面调用resolve(...)方法传入的值.
    //successMessage参数不一定非要是字符串类型,这里只是举个例子
    console.log("Yay! " + successMessage);
});

高级一点的例子

本例展示了Promise的一些机制。testPromise()方法在每次点击<button>按钮时被调用,该方法会创建一个promise 对象,使用window.setTimeout()让Promise等待 1-3 秒不等的时间来填充数据(通过Math.random()方法)。

Promise 的值的填充过程都被日志记录(logged)下来,这些日志信息展示了方法中的同步代码和异步代码是如何通过Promise完成解耦的。

'use strict';
var promiseCount = 0;

function testPromise() {
    let thisPromiseCount = ++promiseCount;

    let log = document.getElementById('log');
    log.insertAdjacentHTML('beforeend', thisPromiseCount +
        ') Started (<small>Sync code started</small>)<br/>');

    // We make a new promise: we promise a numeric count of this promise, starting from 1 (after waiting 3s)
    let p1 = new Promise(
        // The resolver function is called with the ability to resolve or
        // reject the promise
       (resolve, reject) => {
            log.insertAdjacentHTML('beforeend', thisPromiseCount +
                ') Promise started (<small>Async code started</small>)<br/>');
            // This is only an example to create asynchronism
            window.setTimeout(
                function() {
                    // We fulfill the promise !
                    resolve(thisPromiseCount);
                }, Math.random() * 2000 + 1000);
        }
    );

    // We define what to do when the promise is resolved/rejected with the then() call,
    // and the catch() method defines what to do if the promise is rejected.
    p1.then(
        // Log the fulfillment value
        function(val) {
            log.insertAdjacentHTML('beforeend', val +
                ') Promise fulfilled (<small>Async code terminated</small>)<br/>');
        })
    .catch(
        // Log the rejection reason
       (reason) => {
            console.log('Handle rejected promise ('+reason+') here.');
        });

    log.insertAdjacentHTML('beforeend', thisPromiseCount +
        ') Promise made (<small>Sync code terminated</small>)<br/>');
}

使用 XHR 加载图像

另一个用了Promise和XMLHttpRequest加载一个图像的例子可在MDN GitHubpromise-test中找到。 你也可以看这个实例。每一步都有注释可以让你详细的了解Promise和XHR架构。

规范

规范 状态 备注
ECMAScript 2015 (6th Edition, ECMA-262) Promise Standard ECMA标准中的首次定义
ECMAScript Latest Draft (ECMA-262) Promise Living Standard

浏览器兼容性

Feature Chrome Edge Firefox Internet Explorer Opera Safari
Basic Support 32.0 (Yes) 29.01 No 19 7.13
Feature Android Chrome for Android Edge mobile Firefox for Android IE mobile Opera Android iOS Safari
Basic Support 4.4.4 32.0 (Yes) 291 No (Yes) 8.03

一个简单版本的实现

利用观察者模式,只需要通过特定书写方式注册对应状态的事件处理函数,然后更新状态,调用注册过的处理函数即可。这个特定方式就是 then ,done ,fail, always…等方法;更新状态触发时机就是resolve, reject方法。

理论分析:

  • 写一个类对象,维护一个 state,值有3种:”pending”, “resolved”, “rejected”

  • 通过then done fail always方法注册回调处理函数

  • 通过resolve reject分别更新对应状态,并且调用注册函数

github转载链接代码如下:

/**
 * [3种状态]
 * @type {String}
 */

var PENDING = "pending";
var RESOLVED = "resolved";
var REJECTED = "rejected";

/**
 * [Promise类实现]
 * 构造函数传入一个fn,有两个参数,resolve:成功回调; reject:失败回调;
 * reject: 将状态更改为
 * state: 当前执行状态,有pending、resolved、rejected 3种取值(状态存储)
 * doneList: 成功处理函数列表
 * failList: 失败处理函数列表
 * done:cb 注册成功处理函数
 * fail:errorcb 注册失败处理函数
 * then:cb/errocb 同时注册成功和失败处理函数
 * always:cb/errorcb 一个处理注册到成功和失败,都会调用
 * resolve: 更新state为:resolved,并触发绑定的所有成功的回调函数(执行成功处理队列)
 * reject: 更新state为:rejected,并触发绑定的所有失败的回调函数(执行失败处理队列)
 */

var Promise = (function (){
    function Promise(fn){
        this.state = PENDING;
        this.doneList = [];
        this.failList = [];
        this.fn = fn;
        this.fn(this.resolve.bind(this), this.reject.bind(this))
    }

    var p = {
        done: function (cb){
            if(typeof cb == "function")
                this.doneList.push(cb)
            return this;
        },
        fail: function(cb){
            if(typeof cb == "function")
                this.failList.push(cb);
            return this;
        },
        then: function(success, fail){
            this.done(success || noop).fail(fail || noop)
            return this;
        },
        always: function(cb){
            this.done(cb).fail(cb)
            return this;
        },
        resolve: function(){
            this.state = RESOLVED;
            var lists = this.doneList;
            for(var i = 0, len = lists.length; i<len; i++){
                lists[0].apply(this, arguments);
                lists.shift();
            }
            return this;
        },
        reject: function(){
            this.state = REJECTED;
            var lists = this.failList;
            for(var i = 0, len = lists.length; i<len; i++){
                lists[0].apply(this, arguments);
                lists.shift();
            }
            return this;
        }
    }
    for(var k in p){
        Promise.prototype[k] = p[k]
    }

    return Promise;
})();

function noop(){}

results matching ""

    No results matching ""