>

浅谈ES6原生Promise

- 编辑:至尊游戏网站 -

浅谈ES6原生Promise

浅谈ES6原生Promise

2016/08/28 · JavaScript · es6, Promise

原文出处: samchowgo   

ES6标准出炉之前,一个幽灵,回调的幽灵,游荡在JavaScript世界。

正所谓:

世界本没有回调,写的人多了,也就有了})})})})})

Promise的兴起,是因为异步方法调用中,往往会出现回调函数一环扣一环的情况。这种情况导致了回调金字塔问题的出现。不仅代码写起来费劲又不美观,而且问题复杂的时候,阅读代码的人也难以理解。
举例如下:

JavaScript

db.save(data, function(data){ // do something... db.save(data1, function(data){ // do something... db.save(data2, function(data){ // do something... done(data3); // 返回数据 }) }); });

1
2
3
4
5
6
7
8
9
10
db.save(data, function(data){
    // do something...
    db.save(data1, function(data){
        // do something...
        db.save(data2, function(data){
            // do something...
            done(data3); // 返回数据
        })
    });
});

假设有一个数据库保存操作,一次请求需要在三个表中保存三次数据。那么我们的代码就跟上面的代码相似了。这时候假设在第二个db.save出了问题怎么办?基于这个考虑,我们又需要在每一层回调中使用类似try...catch这样的逻辑。这个就是万恶的来源,也是node刚开始广为诟病的一点。

另外一个缺点就是,假设我们的三次保存之间并没有前后依赖关系,我们仍然需要等待前面的函数执行完毕, 才能执行下一步,而无法三个保存并行,之后返回一个三个保存过后需要的结果。(或者说实现起来需要技巧)

不幸的是,在我刚开始接触node的时候,我写了大量这样的hell。

作为一个有时还动下脑子的程序员,我尝试了朴灵大人的eventproxy。后来因为还是写前端代码多一些,我接触了ES6,发现了一个解决回调深渊的利器Promise

其实早在ES6的Promise之前,Qwhen.jsbluebird等等库早就根据Promise标准(参考Promise/A+)造出了自己的promise轮子。
(看过一篇文章,我觉得很有道理。里面说,不要扩展内置的原生对象。这种做法是不能面向未来的。所以这里有一个提示:使用扩展原生Promise的库时,需要谨慎。)

这里仅讨论原生的Promise

=

ES6 Promise

前言

本文旨在简单讲解一下javascript中的Promise对象的概念,特性与简单的使用方法。并在文末会附上一份符合PromiseA+规范的Promise对象的完整实现。

注:本文中的相关概念均基于PromiseA+规范。

相关参考

JavaScript Promise迷你书

Promise/A+规范


Promise对象状态

在详解Promise之前,先来点理论:

Promise/A+规范, 规定Promise对象是一个有限状态机。它三个状态:

  • pending(执行中)
  • fulfilled(成功)
  • reject(拒绝)

其中pending为初始状态,fulfilled和rejected为结束状态(结束状态表示promise的生命周期已结束)。

状态转换关系为:

pending->fulfilled,pending->rejected。

1
pending->fulfilled,pending->rejected。

随着状态的转换将触发各种事件(如执行成功事件、执行失败事件等)。

正文

Promise形式

Promise的长相就像这样子:

JavaScript

var promise = new Promise(function func(resolve, reject){ // do somthing, maybe async if (success){ return resolve(data); } else { return reject(data); } }); promise.then(function(data){ // do something... e.g console.log(data); }, function(err){ // deal the err. })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var promise = new Promise(function func(resolve, reject){
    // do somthing, maybe async
    if (success){
      return resolve(data);
    } else {
      return reject(data);
    }
});
 
promise.then(function(data){
    // do something... e.g
    console.log(data);
}, function(err){
    // deal the err.
})

这里的变量promisePromise这个对象的实例。

promise对象在创建的时候会执行func函数中的逻辑。

逻辑处理完毕并且没有错误时,resolve这个回调会将值传递到一个特殊的地方。这个特殊的地方在哪呢?就是下面代码中的then,我们使用then中的回调函数来处理resolve后的结果。比如上面的代码中,我们将值简单的输出到控制台。如果有错误,则rejectthen的第二个回调函数中,对错误进行处理。

配合上面的有限状态机的理论,我们知道在Promise构造函数中执行回调函数代码时,状态为pendingresolve之后状态为fulfilledreject之后状态为reject

1.Promise简介

在了解javescript中的Promise实现之前有必要先了解一下Promise的概念。

Promise数据流动

以上是promise的第一次数据流动情况。

比较funny的是,promise的then方法依然能够返回一个Promise对象,这样我们就又能用下一个then来做一样的处理。

第一个then中的两个回调函数决定第一个then返回的是一个什么样的Promise对象。

  • 假设第一个then的第一个回调没有返回一个Promise对象,那么第二个then的调用者还是原来的Promise对象,只不过其resolve的值变成了第一个then中第一个回调函数的返回值。
  • 假设第一个then的第一个回调函数返回了一个Promise对象,那么第二个then的调用者变成了这个新的Promise对象,第二个then等待这个新的Promise对象resolve或者reject之后执行回调。

话虽然饶了一点,但是我自我感觉说的还是很清楚的呢。哈哈~

如果任意地方遇到了错误,则错误之后交给遇到的第一个带第二个回调函数的then的第二个回调函数来处理。可以理解为错误一直向后reject, 直到被处理为止。

另外,Promise对象还有一个方法catch,这个方法接受一个回调函数来处理错误。即:

JavaScript

promise.catch(function(err){ // deal the err. })

1
2
3
promise.catch(function(err){
    // deal the err.
})

假设对错误的处理是相似的,这个方法可以对错误进行集中统一处理。所以其他的then方法就不需要第二个回调啦~

什么是Promise?

关于Promise概念的解释,网上的各种资料众说纷纭,这里奉上笔者自己的理解。简单来说,Promise就是一套处理异步事件的方式和流程。promise在英文中的含义是约定,而针对异步事件特性的处理方式与这个含义非常吻合。

控制并发的Promise

Promise有一个”静态方法”——Promise.all(注意并非是promise.prototype), 这个方法接受一个元素是Promise对象的数组。

这个方法也返回一个Promise对象,如果数组中所有的Promise对象都resolve了,那么这些resolve的值将作为一个数组作为Promise.all这个方法的返回值的(Promise对象)的resolve值,之后可以被then方法处理。如果数组中任意的Promisereject,那么该reject的值就是Promise.all方法的返回值的reject值.

很op的一点是:
then方法的第一个回调函数接收的resolve值(如上所述,是一个数组)的顺序和Promise.all中参数数组的顺序一致,而不是按时间顺序排序。

还有一个和Promise.all相类似的方法Promise.race,它同样接收一个数组,只不过它只接受第一个被resolve的值。

为什么要使用Promise?

一个异步事件不会立刻返回结果,这时我们就需要预先规定一些操作,等待异步事件返回结果后,再去使用某种方式让预先规定的操作执行。在javascript的习惯中,我们常用回调函数(callback)去实现上述过程。下面是一个简单的示例:

例1

let asyncFunc = function(callback){

    let num = 100;

    setTimeout(function(){

        num += 100;

        callback(num);

    },2000);

};

function foo(value){

    console.log(value);  //value => 200

}

asyncFunc (foo);

上面就是一个简单的异步操作处理过程,asyncFunc就是一个异步的函数,执行后通过setTimeout方法在2秒返回了一个值,而foo则是一个回调函数,通过传入异步函数并且在返回结果后被调用的方式获取异步操作的结果。这里的回调函数就如同一个事先的约定,在异步操作返回结果后立即被实现。

那么,既然js中已经有处理异步事件的方法,为何还要引入Promise这个新的方式呢?实际上,上面这段代码只是简单展示下回调函数的基础使用,而在真正的使用场景中,我们不得不面对各种十分复杂的局面。通常在一个异步操作返回结果后执行的回调中还要进行另一个异步操作,而同一个异步操作返回结果后要执行的回调函数可不止一个。数个异步操作与回调函数彼此嵌套,时刻挑战者维护和使用者的神经。下面是一个彼此嵌套的例子:

例2

ajax(url1,function(value1){

    foo(value1);

    bar();

});

function foo(value){

    ajax(url2,function(value2){

        do something..

        ajax(url3,function(value3){

            ...

        })

    });

}

function bar(){ do something.. };

上面的例子模拟了一个js中一个常用的异步操作:发送ajax请求数据。在url1请求的回调中使用了foo和bar两个函数,而foo中又发送了url2,url3的请求。。。这样数层嵌套下来,最终导致代码非常的不直观,维护起来难度也直线上升,形成常说的“回调地狱”。

了解了传统上js处理异步操作的复杂和困难后,我们不禁思索,是否有方法能够更加简洁,直观的去解决异步操作的种种问题?答案就是我们这篇文章的主角:Promise。

将其他对象变为Promise对象

Promise.resovle方法,可以将不是Promise对象作为参数,返回一个Promise对象。

有两种情形:

  1. 假设传入的参数没有一个.then方法,那么这个返回的Promise对象变成了resolve状态,其resolve的值就是这个对象本身。
  2. 假设传入的参数带有一个then方法(称为thenable对象), 那么将这个对象的类型变为Promise,其then方法变成Promise.prototype.then方法。

2. Promise的特性及使用

在PromiseA+规范中做出了这样定义:promise是一个包含了兼容Promise规范then方法的对象或函数,与Promise最主要的交互方法是通过将函数传入它的then方法从而获取得Promise最终的值或Promise最终最拒绝(reject)的原因。

  这段定义有两个重点:1.Promise是一个对象或函数  2.它有一个then方法,能够获取prmose的最终结果。下面我们就来实际看一下Promise到底是如何处理异步事件的,我们将上面的例1使用Promise进行一下改写:

例3

let p = new Promise(function(resolve,reject){

    let value = 100;

    setTimeout(function(){

        value += 100;

        resolve(value);

    },2000);

});

p.then(function(value){

    console.log(value);      //value => 200

},function(err){

    do something...

});

初看之下其实并没有太大区别,但实际上Promise的威力在更复杂的场景下才能更好的发挥。我们先针对这个简单的例子来讲解下Promise的使用

首先通过 new 关键字实例化一个Promise对象,在这个对象中传入一个要执行异步操作的函数。这个函数包含两个形参:resolve和reject。这两个形参是Promise中定义的2个函数,分别在异步事件成功和失败时调用。例3中我们在2秒后调用了resolve函数,代表着异步事件成功,返回一个值。而在我们实例化Promise对象的同时,我们又调用了这个实例的then方法。then方法可以说是Promise方法中的核心,它即代表着Promise约定的这层含义,在then方法中接收2个函数作为参数,分别在异步事件成功时或失败时执行,并且两个函数的参数正是异步事件成功时返回的值或失败时原因。

其实,使用Promise对象来处理异步事件比起使用传统的回调函数的一个优点在于:Promise规范了处理异步事件的流程。我们不必再深入异步事件的内部,去分析种种状态变化后对应的回调究竟如何调用,也不必过多考虑异步事件内部发生错误时该如何捕获,我们只需要在合适的时候通知Promise返回成功或失败状态,剩下的事统统交给Promise去解决。

以上我们大致了解了Promise的处理流程,在详细讲解Promise对象中的方法之前有必要先了解一下Promise的状态概念。

一个Promise对象在实例化后可能拥有以下3种状态的其中之一:

Fulfilled - 当传入的异步事件成功返回值时的状态

Rejected - 当传入的异步事件失败或产生异常时的状态

Pending -  当传入的异步事件还没有结果返回时的状态

注意,任何时候Promise对象都只能处于以上其中状态的一种,当Promise对象处于Pending状态时,它可以转化成Fulfilled 或Rejected 状态,而当Promise对象处于Fulfilled 或Rejected状态时,它不能再转化成其他状态。

可以用一张图来直白的表示上面这段话

图片 1

                                                     (图片取自Promise迷你书)

在了解了Promise的三种状态后 ,接下来可以详细了解下Promise对象的几个方法

Promise是解决异步的方案吗?

最后说一点很重要的事:Promise的作用是解决回调金字塔的问题,对于控制异步流程实际上没有起到很大的作用。真正使用Promise对异步流程进行控制,我们还要借助ES6 generator函数。(例如Tj大神的co库的实现)。

然而ES7将有一个更加牛逼的解决方案:async/await,这个方案类似于co,但是加了原生支持。拭目以待吧。

resolve()

resolve方法是在一个Promise对象实例化时传入的任务函数的第一个参数,它的作用是让Promise进入“Fulfilled ”状态,resolve方法只接受一个参数,即异步事件的返回值value。

文档

mozilla开发者文档


以上。一点微小的见解,谢谢大家。

1 赞 5 收藏 评论

图片 2

reject()

reject方法与resolve方法正好相反,它是在一个Promise对象实例化时传入的任务函数的第二个参数,它的作用是让Promise进入“Rejected”状态,reject方法同样只接受一个参数,即异步事件失败或异常的原因reason。

Promise.prototype.then()

then方法是Promise对象方法的重中之重,它是Promise实例的方法,用来注册Promise对象成功时执行的回调函数(onFulfilled)和失败时执行的回调函数(onRejected)。一个then方法的返回值仍然是一个Promsie对象。因此,then方法支持链式调用,也就是一个一个then方法的返回值可以继续调用then。而相链接的then方法中,在上一个then方法的onFulfilled或onRejected回调函数中通过 return value(reason)的方式,把这个结果作为下一个then中的回调函数的参数被接收。onFulfilled和onRejected函数的返回值可以是任何javascript值,甚至一个Promise对象的成功或失败时的回调函数可以返回一个新的Promise对象。这样的特性使得例2中那种复杂的异步事件嵌套的场景处理得以简化。下面是使用Promise来重写的例2:

例4

let p1 = new Promise(function(resolve,reject){

    ajax(url1,function(value1){

        resolve(value1);

    });

});

p1.then(function(value1){

    return new Promise(function(resolve,reject){

        ajax(url2,function(value2){

            do something..

            resolve(value2);

        });

    })

}).then(function(value2){

    return new Promise(function(resolve,reject){

        ajax(url3,function(value3){

            ...

        });

    })

});

p1.then(bar);

function bar(){do something...};

可以看出,使用Promise改写后的代码结构上更加清晰,它把层层嵌套的函数转化成链式的调用then方法的形式,这样可以非常清晰的看出事件间的关系和执行顺序,大大降低了日后代码使用和维护的难度。

关于then方法还有几点补充:

1. then方法中的onFulfilled和onRejected方法都是可以省略的。

2. 当一个Promise失败返回了reason,而then方法中没有定义onRejected函数时,这个reason会被链式调用的下一个then方法的onRejected方法接收。

3. 一个Promise实例可以调用多次then方法,这些then注册的onFulfilled和onRejected函数会按照注册的顺序执行。

Promise.prototype.catch()

catch方法是一个then方法的语法糖,它只接受一个失败处理函数onRejected,实际上等同于以下代码:

new Promsie.then(null,function(){

    do something...

})

Promise.all()

all方法是Promsie对象的静态方法,使用方式是 Promise.all()。all方法接收的参数为一个包含数个Promise对象实例的数组,并返回一个新的Promise实例。当数组中所有的Promse实例都返回结果后,将所有数组中的Promise实例的成功返回值传入一个数组,并将这个数组注入到all方法返回的新实例的then方法中。下面是一个all方法的使用实例:

例5

let promiseArr = [

    new Promise(function(resolve,reject){

        setTimeout(function(){

            resolve(100)

        },1000)

    }),

    new Promise(function(resolve,reject){

        setTimeout(function(){

            resolve(200)

        },500)

    })

]

Promise.all(promiseArr).then(function(valArr){

    console.log(valArr)    // valArr  => [100,200]

},function(err){

    do something...

})

all方法值得注意的有两点:

1.数组中所有promise实例都成功后的返回值,在valArr中的顺序是按照promiseArr 中promise实例的顺序来排列的。

2.当任何一个promise失败后,all方法直接将返回的Promise对象的状态变为Rejected,并调用then方法的onRejected函数,把失败的原因传递出来。

Promise.resolve()

Promsie对象本身存在一个resolve方法,它的作用是立刻返回一个状态为Fulfilled的Promise对象实例。如果你在这个resolve方法中传入的是一个Promise实例的话,那么resolve方法会保持这个Promise实例的状态,并根据它最后返回的状态来调用resolve方法返回的Promise实例then方法的onResolve或onRejected函数。

其实这个方法最常用的场景是讲一个普通的值转换成一个Promise实例。一般来说不是很常用。

Promise.reject()

与Promise.resolve()相反,它的作用是立刻返回一个状态为Rejected的Promise对象实例。实际上这个方法是一个语法糖,它等同于以下代码:

new Promise(function(resolve,reject){

    reject(reason);

})

以上就是一个ES6中的Promise对象中所包含的常用方法。

3. 一个符合PromiseA+规范的Promise对象的完整实现

  想必看到这里的一些读者会不禁思考,Promise对象究竟是如何实现的呢?我个人参考了一些资料实现了一个符合PromiseA+规范的Promise对象,把源代码贴在下面,有兴趣的朋友可以参考一下,实际上代码本身并不是很多,各位看完以后可以尝试用自己的方式再实现一遍。同时附上一个测试工具,里面包含了几百个测试用例,用来测试我们自己写的Promise是否完美的符合PromiseA+规范。

Compliances tests for Promises/A+

使用的方法很简单

npm i -g promises-aplus-tests

promises-aplus-tests Promise.js

安装后运行你的js文件就可以测试你的代码是否符合规范了。

下面就是我实现的Promise对象的代码

function MyPromise(task) {

    const _this = this;

    _this.status = 'pending';  //设定初始状态

    _this.value = undefined;

    _this.onFulfilledsList = [];  //onFulfilled函数序列

    _this.onRejectedsList = [];  //onRejected函数序列

    function resolve(value) {

        if (value instanceof MyPromise) {

            return value.then(resolve, reject);

        }

        //异步执行resolve或reject方法,保证代码的统一性和注册的回调函数按照正确的顺序执行

            if (_this.status === 'pending') {

                _this.status = 'fulfilled';

                _this.value = value;

                _this.onFulfilledsList.forEach(cb => cb(value))

            }

    }

    function reject(reason) {

            if (_this.status === 'pending') {

                _this.status = 'rejected';

                _this.reason = reason;

                _this.onRejectedsList.forEach(cb => cb(reason))

            }

    }

    try {

        task(resolve, reject);

    } catch (err) {

        throw new Error(err);

    }

}

function resolvePromise(promise2, x, resolve, reject) {

    if (x === promise2) {

        return reject(new TypeError('循环引用'));

    }

    //如果返回的是一个thenable对象,即一个拥有then方法的对象,那么使用它的then方法去获得它的最终返回值。目的是为了兼容其他Promise库

    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {

        let then, called;

        try {

            then = x.then;

            if (typeof then === 'function') {

                then.call(x, function (newx) {

                    if (called) return;  //防止重复调用

                    called = true;

                    resolvePromise(promise2, newx, resolve, reject);

                }, function (err) {

                    if (called) return;

                    called = true;

                    return reject(err);

                });

            } else {

                resolve(x);

            }

        } catch (err) {

            if (called) return;

            called = true;

            reject(err);

        }

    } else {

        resolve(x);

    }

}

MyPromise.prototype.then = function (onFulfilled, onRejected) {

    const _this = this;

    let promise2;

    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (data) {

        return data;

    };

    onRejected = typeof onRejected === 'function' ? onRejected : function (data) {

        throw data;

    };

    //为了支持同步代码,当then方法注册的时候如果Promise的状态已经改变,那么立即执行对应的函数

    if (_this.status === 'fulfilled') {

        promise2 = new MyPromise(function (resolve, reject) {

          setTimeout(function () {

            let x;

            try {

                x = onFulfilled(_this.value);

                resolvePromise(promise2, x, resolve, reject);

            } catch (err) {

                reject(err);

            }

          })

        })

    }

    if (_this.status === 'rejected') {

        promise2 = new MyPromise(function (resolve, reject) {

         setTimeout(function () {

            let x;

            try {

                x = onRejected(_this.reason);

                resolvePromise(promise2, x, resolve, reject);

            } catch (err) {

                reject(err);

            }

         )}

        })

    }

    if (_this.status === 'pending') {

        promise2 = new MyPromise(function (resolve, reject) {

            _this.onFulfilledsList.push(function (value) {

                setTimeout(function () {

                let x;

                try {

                    x = onFulfilled(value);

                    resolvePromise(promise2, x, resolve, reject);

                } catch (err) {

                    reject(err);

                }

                })

            });

            _this.onRejectedsList.push(function (reason) {

               setTimeout(function () {

                try {

                    let x = onRejected(reason);

                    resolvePromise(promise2, x, resolve, reject);

                } catch (err) {

                    reject(err);

                }

            })

        });

        })

    }

    return promise2;  //返回一个新的Promise实例,以便支持链式调用

};

MyPromise.prototype.catch = function (onRejected) {

    this.then(null, onRejected);

};

MyPromise.all = function (someValue) {

    let resolveValArr = [];

    let count = promiseLen = 0;

    let promise2;

    promise2 = new MyPromise(function (resolve, reject) {

        let iNow = 0;

        try {

            for (let item of someValue) {

                if (item !== null && typeof item === "object") {

                    try {

                        let then = item.then;

                        let index = iNow;

                        if (typeof then === 'function') {

                            promiseLen++;

                            then.call(item, function (value) {

                                resolveValArr[index] = value;

                                if (++count === promiseLen) {

                                    resolve(resolveValArr)

                                }

                            }, function (err) {

                                reject(err);

                            });

                        }

                    } catch (err) {

                        resolveValArr[iNow] = item;

                    }

                } else {

                    resolveValArr[iNow] = item;

                }

                iNow++;

            }

            if (iNow === 0) {

                return resolve(someValue);

            }

            if (promiseLen === 0) {

                return resolve(resolveValArr);

            }

        } catch (err) {

            reject(new TypeError('无法遍历的类型!'));

        }

    });

    return promise2;

};

MyPromise.race = function (someValue) {

    let promise2;

    promise2 = new MyPromise(function (resolve, reject) {

        let iNow = 0;

        try {

            for (let item of someValue) {

                if (item !== null && typeof item === "object") {

                    try {

                        let then = item.then;

                        then.call(item, function (value) {

                            resolve(value);

                        }, function (err) {

                            reject(err);

                        });

                    } catch (err) {

                        resolve(item);

                        break;

                    }

                } else {

                    resolve(item);

                    break;

                }

                iNow++;

            }

            if (iNow === 0) {

                return resolve(someValue);

            }

        } catch (err) {

            reject(new TypeError('无法遍历的类型!'));

        }

    });

    return promise2;

};

MyPromise.resolve = function (value) {

    let promise2;

    if (value !== null && (typeof value === 'object' || typeof value === 'function')) {

        promise2 = new MyPromise(function (resolve, reject) {

            try {

                let then = value.then;

                if (typeof value.then === 'function') {

                    then.call(value, function (data) {

                        resolve(data);

                    }, reject);

                } else {

                    resolve(value);

                }

            } catch (err) {

                reject(err);

            }

        })

    } else {

        promise2 = new MyPromise(function (resolve) {

            resolve(value);

        })

    }

    return promise2;

};

MyPromise.reject = function (reason) {

    return new MyPromise(function (resolve, reject) {

        reject(reason);

    })

};

module.exports = MyPromise;

//这是为了让代码能够测试而开放的接口,详见promises-aplus-tests中的相关描述

MyPromise.deferred = MyPromise.defer = function () {

    let deferred = {};

    deferred.promise = new MyPromise(function (resolve, reject) {

        deferred.resolve = resolve;

        deferred.reject = reject;

    });

    return deferred

};

-

尾声

本文参考了很多资料,如果你看到其他文章有类似的观点非常正常,不过笔者尽量使用了自己的理解去阐述Promise的相关知识。如果你发现本文中有哪些疏漏,欢迎发私信给我进行斧正。同时也可以在下面留言给我,我会一一查看,尽量回复。

本文由硬件数码发布,转载请注明来源:浅谈ES6原生Promise