ES6新特性Promise详解

在ES6当中添加了很多新的API其中很值得一提的当然少不了Promise,因为Promise的出现,很轻松的就给开发人员解决了异步回调地狱的问题,废话不多,直接上代码:

在以前我们有个一个异步函数,而且还会有返回值给别的功能函数使用,函数如下:

//异步函数延时两秒执行

function testA() {

  setTimeout(function () {

    return 200;

  },2000)

}

//其它功能函数

function testB(val) {

  console.log(val)

}

//将testA异步函数的返回值当成参数来使用

testB(testA());//遗憾的是这里输出的是undefined,原因是因为异步函数的调用是在主程序之后

//想要使用testA异步函数的值,解决方案有两种

// 1.那么我们只能将功能函数的功能集成到异步函数A当中来使用了

function testA() {

  setTimeout(function () {

    console.log(200);//功能模块

  },2000)

}

testA();

// 2.把testB当成testA的回调函数来使用

function testA(callback) {

  setTimeout(function () {

    callback(200);//功能模块

  },2000)

}

function callback(val) {

  console.log(200)

}

testA(callback);

但是这两种方式存在的问题很多,例如层级之间的嵌套太深,代码很臃肿,可读性与可维护性都极低。

直到ES6当中Promise的出现,这样子的事情就变得很简单了:

//要想创建一个Promise对象、可以使用new来调用Promise的构造器来进行实例化,实例化时可传一个函数

//对通过new生成的promise对象为了设置其值在 resolve(成功) / reject(失败)时调用的回调函数

// 可以使用promise.then() 实例方法。

//promise.then(onFulfilled, onRejected)

//resolve(成功)时,onFulfilled 会被调用

//reject(失败)时,onRejected 会被调用

let P1=new Promise((resolve,reject)=>{

  setTimeout(function () {

    resolve(200)

  },2000)

}).then(res=>{

  console.log(res)

})

通过Promise的使用上面两种情况是不是就变得很简单了呢

但是,Promise的实现到底是一个什么样的原理呢?下面我将会来一一的解析:

因为Promise要以实例化的方式来使用,所以需要创建一个类MyPromise,示例如下:

class MyPromise{

    constructor(callback){

        try{

            callback(this.resolve.bind(this),this.reject.bind(this))

        }catch (err){

            this.reject(err)

        }

        this.status='pending';//状态——pending-等待,fulfilled-成功,rejected-失败

        this.onRejectedCallback=[];//失败回调集合

        this.onFulfilledCallback=[];//成功回调集合

        this.value=undefined;//成功返回值

        this.reason=undefined;//失败返回值

    }

    resolve(res){

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

            this.status='fulfilled';

            this.value=res;

            this.onFulfilledCallback.forEach((item)=>{

                item(this.value);

            })

        }

    }

    reject(err){

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

            this.status='rejected';

            this.reason=err;

            this.onRejectedCallback.forEach((item)=>{

                item(this.reason);

            })

        }

    }

    then(resolve,reject){

        resolve = typeof resolve == 'function'?resolve:function(value){return value};

        reject = typeof reject=='function'?reject:function(reason){

            throw reason;

        };

        this.resolve=resolve;

        this.reject=reject;

        let self=this;

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

            resolve(self.value);

        }

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

            reject(self.reason);

        }

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

            self.onFulfilledCallback.push(resolve);

            self.onRejectedCallback.push(reject);

        }

    }

}

//测试

let test=new MyPromise(function (resolve,reject) {

        setTimeout(function () {

            resolve(1000)

        },2000)

})

test.then(res=>{

    console.log(res)

},err=>{

    console.log(err+'err')

})

按照上述的示例,一个简单的基础Promise就实现了,但是我们会发现ES6提供的Promise当中可以按照jQuery一样的链式调用,如下:

var origin=new Promise((resolve,reject)=>{

    setTimeout(function () {

        resolve('成功')

    },2000)

})

//链式调用

origin.then(res=>{

    console.log(res)

    return res;

}).then(res=>{

    console.log(res)

})

在jQuery当中的链式调用大家都清楚,jQuery之所以能够实现链式调用是因为每个jQuery方法返回的JQuery对象本身,那么在Promise里,其实每个Promise的then方法里面返回的都是一个全新的promise,原理如下:

class MyPromise {

    constructor(callback) {

        this.status = 'pending';//状态——pending-等待,fulfilled-成功,rejected-失败

        this.onRejectedCallback = [];//失败回调集合

        this.onFulfilledCallback = [];//成功回调集合

        this.value = undefined;//成功回调

        this.reason = undefined;//失败回调

        try {

            callback(this.resolve.bind(this), this.reject.bind(this))

        } catch (err) {

            this.reject(err)

        }

    }

    resolve(res) {

        if (res instanceof MyPromise) {

            return res.then(res.resolve.bind(this),res.reject.bind(this));

        }

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

            this.status = 'fulfilled';

            this.value = res;

            this.onFulfilledCallback.forEach((item) => {

                item(this.value);

            })

        }

    }

    reject(err) {

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

            this.status = 'rejected';

            this.reason = err;

            this.onRejectedCallback.forEach((item) => {

                item(this.reason);

            })

        }

    }

    resolvePromise(returnPromise, x, resolve, reject) {

        let then;

        //如果x就是returnPromise

        if (returnPromise === x) {

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

        }

        if (x instanceof MyPromise||x instanceof Promise) {

            if(x instanceof Promise){

                x.then(function (val) {

                    resolve(val)

                })

            }else{

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

                    x.then(function (y) {

                        this.resolvePromise(returnPromise, y, resolve, reject);

                    }, reject);

                } else if (x.status == 'fulfilled') {

                    resolve(x.value);

                } else if (x.status == 'rejected') {

                    reject(x.reason);

                }

            }

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

            try {

                then = x.then;

                if (typeof then == 'function') {

                    then.call(x, function (y) {

                        this.resolvePromise(returnPromise, y, resolve, reject)

                    }, reject);

                }

            } catch (e) {

                reject(e);

            };

        } else {

            resolve(x);

        }

    }

    then(resolve, reject) {

        resolve = typeof resolve == 'function' ? resolve : function (value) {

            return value

        };

        reject = typeof reject == 'function' ? reject : function (reason) {

            throw reason;

        };

        this.resolve = resolve;

        this.reject = reject;

        let returnPromise;//返回的新promise

        let self = this;

        if (this.status == 'pending') {//等待状态

          //创建返回的新的MyPromise

          //这里通过returnPromise,returnResolve,returnReject等命名主要是为了区分是之前的与新的返回的MyPromise对象的属性

            returnPromise = new MyPromise(function (returnResolve, returnReject) {

                self.onFulfilledCallback.push(function () {

                    let x = resolve(self.value);

                    self.resolvePromise(returnPromise, x, returnResolve, returnReject);

                });

                self.onRejectedCallback.push(function () {

                    let x = reject(self.reason);

                    self.resolvePromise(returnPromise, x, returnResolve, returnReject);

                });

            });

        }

        if (this.status == 'fulfilled') {//成功

          //创建返回的新的MyPromise

            returnPromise = new MyPromise(function (returnResolve, returnReject) {

                let x = resolve(self.value);

                self.resolvePromise(returnPromise, x, returnResolve, returnReject);

            });

        }

        if (this.status == 'rejected') {//失败

            //创建返回的新的MyPromise

            returnPromise = new MyPromise(function (returnResolve, returnReject) {

                let x = reject(self.reason);

                self.resolvePromise(returnPromise, x, returnResolve, returnReject);

            });

        }

        //返回新的MyPromise

        return returnPromise;

    }

}

//测试代码

let test=new MyPromise(function (resolve,reject) {

        setTimeout(function () {

            resolve(1000)

        },1000)

})

test.then(res=>{

    console.log(res)

    return res+200

},err=>{

    console.log(err+'err')

}).then(res=>{

    console.log(res);

})

按照以上代码一样,每个then方法都返回一个全新的MyPromise方法,于是就基本实现了一个ES6中Promise的链式调用。

Promise.all

在我们的ES6中还有Promise.all这样一个方法,该方法主要是传入一个promise集合,返回一个全新的Promise实例,该全新的Promise实例的返回值为当集合当中所有的集合的所有的Promise都成功时才会返回一个对应的每个Promise成功的返回值的集合,但是只要有一个Promise失败,就会马上走到整个Promise.all包装后的新Promise对象的失败里面,ES6的使用方法如下:

1.全成功

var winnerPromise = new Promise(function (resolve,reject) {

    setTimeout(function () {

        resolve('this is winner');

    }, 800);

});

var loserPromise = new Promise(function (resolve,reject) {

    setTimeout(function () {

        resolve('this is loser');

    }, 1000);

});

Promise.all([winnerPromise, loserPromise]).then(function (value) {

    console.log(value);    // => 'this is winner'

}).catch(err=>{

    console.log(err);

});

//输出结果

[ 'this is winner', 'this is loser' ]

2.有一个失败

var winnerPromise = new Promise(function (resolve,reject) {

    setTimeout(function () {

        resolve('this is winner');

    }, 800);

});

var loserPromise = new Promise(function (resolve,reject) {

    setTimeout(function () {

        reject('this is loser');

    }, 1000);

});

Promise.all([winnerPromise, loserPromise]).then(function (value) {

    console.log(value);    // => 'this is winner'

}).catch(err=>{

    console.log(err);

});

//输出

this is loser

对其原理解析:

因为该方法不需要实例化就能使用,于是在我们原本的 MyPromise类上面添加这样一个静态方法

static all(arr) {

        return new MyPromise((resolve, reject) => {

            let result = [];

            let resolveIndex = 0;

            let resolved = function (index) {

                return function (data) {

                    result[index] = data;

                    resolveIndex++;

                    if (resolveIndex === arr.length) {

                        resolve(result)

                    }

                }

            };

            arr.forEach((item, index) => {

                item.then(resolved(index), err => {

                    reject(err)

                })

            });

        })

    }

以上代码就在我前面写的MyPromise类中实现了一个ES6的all方法。

Promise.race

在我们的ES6中还有Promise.race这样一个方法,该方法主要是传入一个promise集合,返回一个全新的Promise实例,与Promise.all很相似,但是不同的是,Promise.race方法只要传入集合中有一个成功或者失败,返回的全新的Promise实例就马上成功或者失败。

ES6代码如下:

var winnerPromise = new Promise(function (resolve,reject) {

    setTimeout(function () {

        resolve('this is winner');

    }, 800);

});

var loserPromise = new Promise(function (resolve,reject) {

    setTimeout(function () {

        reject('this is loser');

    }, 1000);

});

Promise.race([winnerPromise, loserPromise]).then(function (value) {

    console.log(value);    // => 'this is winner'

}).catch(err=>{

    console.log(err);

});

//输出

this is winner

因为该race方法不需要实例化就能使用,于是同样在我们的MyPromise上面添加一个static方法,原理解析代码片段如下:

static race(arr) {

        return new MyPromise((resolve, reject) => {

            arr.forEach((item, index) => {

                item.then(res => {

                    resolve(res);

                }, err => {

                    reject(err)

                })

            });

        })

    }

Promise.catch

ES6中Promise.catch方法其实就是和then方法的reject方法是一样的应用场景,就是当Promise实例在只能reject的回调函数,ES6代码如下:

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

  setTimeout(function () {

      reject(2000)

  },2000)

})

test.then(res=>{

  console.log(res)

}).catch(err=>{

  console.log(err);

})

//输出2000

在MyPromise当中进行原理解析,代码片段如下:

catch(catchFunc) {

        return this.then(null, catchFunc);

    }

以上就是我对于ES6当中Promise的一些理解,当然在代码当中肯定有很多的不足,欢迎大家多多交流,多多指正,

完整代码Github地址:https://github.com/RanCW/promiseDemo

参考文献:

JavaScript Promise迷你书(中文版)

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,676评论 1 56
  • 特点 Promise能将回调分离出来,在异步操作执行之后,用链式方法执行回调,虽然es5用封装函数也能实现,但是如...
    一二三kkxx阅读 610评论 0 1
  • 前言 本文旨在简单讲解一下javascript中的Promise对象的概念,特性与简单的使用方法。并在文末会附上一...
    _暮雨清秋_阅读 2,174评论 0 3
  • 本文适用的读者 本文写给有一定Promise使用经验的人,如果你还没有使用过Promise,这篇文章可能不适合你,...
    HZ充电大喵阅读 7,285评论 6 19
  • 一· 什么是Promise Promise是抽象异步处理对象以及对其进行各种操作的组件。 其实这样说起来还是比较抽...
    时光与少年已陈旧阅读 934评论 0 0