来,用ES6写个Promise吧

本文采用es6语法实现Promise基本的功能, 适合有javascript和es6基础的读者,如果没有,请阅读

回调函数

在日常开发中,通常会使用ajax请求数据,拿到数据后,对数据进行处理。

但是,假设你需要多次ajax请求数据,并且每次ajax请求数据都是基于上一次请求数据作为参数,再次发出请求,于是代码可能就成了这样:

function dataAjax() {
    $.ajax({
        url: '/woCms/getData',
        type: 'get',
        success: function (data) {
            reChargeAjax(data);
        }
    });
}
function reChargeAjax(data) {
    $.ajax({
        url: '/woCms/reCharge',
        type: 'post',
        data: { data },
        success: function (data) {
            loginAjax(data);
        }
    });
}
....

很明显,这样的写法有一些缺点:

  • 如果需求存在多个操作并且层层依赖的话,那这样的嵌套就可能存在多层,并且每次都需要对返回的数据进行处理,这样就严重加大了调试难度,代码不是很直观,并且增加了后期维护的难度
  • 这种函数的层层嵌套,又称之为回调地域,为了解决这种形式存在的缺点,并支持多个并发的请求,于是Promise就出现了

Promise是什么

  • Promise是一种异步流程的控制手段。
  • Promise对象能够使我们更合理、更规范的处理异步流程操作.

Promise基本用法

let pro = new Promise(function (resolve, reject) {
});
  • Promise是一个全局对象,也就是一个类
  • 可通过new关键词创建一个Promise实例
  • Promise构造函数传递一个匿名函数, 匿名函数有2个参数: resolve和reject
  • 两个参数都是方法,resolve方法处理异步成功后的结果
  • reject处理异步操作失败后的原因

Promise的三种状态

  • pending: Promise 创建完成后,默认的初始化状态
  • fulfilled: resolve方法调用时,表示操作成功
  • rejected:reject方法调用时,表示操作失败
Promise.png

状态只能从初始化 -> 成功或者初始化 -> 失败,不能逆向转换,也不能在成功fulfilled 和失败rejected之间转换。

Promise 类构造

好了,目前我们知道了Promise的基础定义和语法,那么我们就用代码来模拟Promise的构造函数和内部实现吧

class Promise {
    // 构造函数,构造时传入一个回调函数
    constructor(executor) {
        this.status = "pending";// promise初始化状态为pending
        this.value = undefined;// 成功状态函数的数据
        this.reason = undefined;// 失败状态的原因

        // new Promise((resolve,reject)=>{});
        let resolve = data => {
            // 成功状态函数,只有当promise的状态为pending时才能修改状态
            // 成功或者失败状态函数,只能调用其中的一个
            if (this.status === "pending") {
                this.status = "fulfilled";
                this.value = data;
            }
        }
        let reject = err => {
            if (this.status === "pending") {
                this.status = "rejected";
                this.reason = err;
            }
        }
    }
    executor(resolve, rejcte);
}

在Promise的匿名函数中传入resolve, rejcte这两个函数,分别对应着成功和失败时的处理,根据Promise规范,只有当Promise的状态是初始化状态是才能修改其状态为成功或者失败,并且只能转为成功或者失败。

then方法

在了解Promise创建和状态之后,我们来学习Promise中一个非常重要的方法:then方法

then()方法:用于处理操作后的程序,那么它的语法是什么样子的呢,我们一起来看下

pro.then(function (res) {
    //操作成功的处理
}, function (error) {
    //操作失败的处理
}); 

那么then函数该如何定义呢?

很明显,then属于Promise原型上的方法,接收2个匿名函数作为参数,第一个是成功函数,第二个是失败函数。

也就是说当Promise状态变为成功的时候,执行第一个函数。当状态变为失败的时候,执行第二个函数。因此then函数可以这样定义:

then(onFulFiled, onRejected) {
    // promise 执行返回的状态为成功态
    if (this.status === "fulfilled") {
        // promise执行成功时返回的数据为成功态函数的参数
        onFulFiled(this.value);
    }
    if (this.status === "rejected") {
        // promise 执行失败是返回的原因为失败态函数的参数
        onRejected(this.reason);
    }
}

Promise then 异步代码队列执行

但是我们现在来看这样一段Promise代码:

let pro = new Promise((resolve, reject) => {
    setTimeout(function () {
        resolve("suc");
    }, 1000);
});
// 同一个promise实例多次调用then函数
pro.then(data => {
    console.log("date", data);
}, err => {
    console.log("err", err);
});
pro.then(data => {
    console.log("date", data);
}, err => {
    console.log("err", err);
})

输出结果为
date suc
date suc

这样Promise中异步调用成功或者失败状态函数,并且Promise实例多次调用then方法,我们可以看到最后输出多次成功状态函数中的内容,而此时:

  • Promise中异步执行状态函数,但Promise的then函数是还是pending状态
  • Promise实例pro多次调用then函数,当状态是pending的时候依然会执行状态函数

那么这块在then函数中是如何处理pending状态的逻辑呢?

采用发布订阅的方式,将状态函数存到队列中,之后调用时取出。

class Promise{
    constructor(executor){
        // 当promise中出现异步代码将成功态或者失败态函数封装时
        // 采用发布订阅的方式,将状态函数存到队列中,之后调用时取出
        this.onFuiFiledAry = [];
        this.onRejectedAry = [];

        let resolve = data => {
            if (this.status === "pending") {
                ...
                // 当状态函数队列中有存放的函数时,取出并执行,队列里面存的都是函数
                this.onFulFiledAry.forEach(fn => fn());
            }
        }
        let reject = err => {
            if (this.status === "pending") {
                ...
                this.onRejectedAry.forEach(fn => fn());
            }
        }
    }
    then(onFuiFiled, onRejected) {
        ...
        if (this.status === "pending") {
            this.onFuiFiledAry.push(() => {
                onFuiFiled(this.value);
            });
            this.onRejectedAry.push(() => {
                onRejected(this.reason);
            });
        }
    }
}

Promise then 函数返回一个新的Promise

看过jQuery源码的童鞋肯定都清楚,jQuery是如何实现链式调用的呢?对,就是this,jQuery通过在函数执行完成后通过返回当前对象的引用,也就是this来实现链式调用。

我们先看看一个例子,假设Promise用this来实现链式调用,会出现什么情况呢?

let pro = new Promise(function (resolve, reject) {
    resolve(123);
});
let p2 = pro.then(function () {
    // 如果返回this,那p和p2是同一个promise的话,此时p2的状态也应该是成功
    // p2状态设置为成功态了,就不能再修改了,但是此时抛出了异常,应该是走下个then的成功态函数
    throw new Error("error");
});
p2.then(function (data) {
    console.log("promise success", data);
}, function (err) {
    // 此时捕获到了错误,说明不是同一个promise,因为promise的状态变为成功后是不能再修改状态
    console.log("promise or this:", err);
})

很明显,Promise发生异常,抛出Error时,p2实例的状态已经是失败态了,所以会走下一个then的失败态函数,而结果也正是这样,说明Promise并不是通过this来实现链式调用。

那Promise中的链式调用是如何实现的呢?

结果是,返回一个新的Promise.

then(onFuiFiled, onRejected) {
    let newpro;
    if (this.status === "fulfilled") {
        newpro = new Promise((resolve, reject) => {
            onFuiFiled(this.value);
        });
    }
    if (this.status === "rejected") {
        newpro = new Promise((resolve, reject) => {
            onRejected(this.reason);
        });
    }
    if (this.status === "pending") {
        newpro = new Promise((resolve, reject) => {
            this.onFuiFiledAry.push(() => {
                onFuiFiled(this.value);
            });
            this.onRejectedAry.push(() => {
                onRejected(this.reason);
            });
        });
    }
    return newpro;
}

Promise then函数返回值解析

好了,我们继续将目光放在then函数上,then函数接收两个匿名函数,那假设then函数返回的是数值、对象、函数,或者是promise,这块Promise又是如何实现的呢?

来,我们先看例子:

let pro = new Promise((resolve, reject) => {
    resolve(123);
});
pro.then(data => {
    console.log("then-1",data);
    return 1;
}).then(data => {
    console.log("then-2", data);
});

例子输出的结果是
then-1 123
then-2 1

也就是说Promise会根据then状态函数执行时返回的不同的结果来进行解析:

  • 如果上一个then函数返回的是数值、对象、函数的话,是会直接将这个数值和对象直接返回给下个then;
  • 如果then返回的是null,下个then获取到的也是null;
  • 如果then返回的是一个新的promise的话,则根据新的promise的状态函数来确定下个then调用哪个状态函数,如果返回的新的promise没有执行任何状态函数的话,则这个promise的状态是pending

那这块,我们该如何实现呢?来,看具体代码吧

function analysisPromise(promise, res, resolve, reject) {
    // promise 中返回的是promise实例本身,并没有调用任何的状态函数方法
    if (promise === res) {
        return reject(new TypeError("Recycle"));
    }
    // res 不是null,res是对象或者是函数
    if (res !== null && (typeof res === "object" || typeof res === "function")) {
        try {
            let then = res.then;//防止使用Object.defineProperty定义成{then:{}}
            if (typeof then === "function") {//此时当做Promise在进行解析
                then.call(res, y => {
                    // y作为参数,promise中成功态的data,递归调用函数进行处理
                    analysisPromise(promise, y, resolve, reject);
                }, err => {
                    reject(err);
                })
            } else {
                // 此处then是普通对象,则直接调用下个then的成功态函数并被当做参数输出
                resolve(res);
            }
        } catch (error) {
            reject(error);
        }
    } else {
        // res 是数值
        resolve(res);
    }
}
then(onFuiFiled, onRejected) {
    let newpro;
    if (this.status === "fulfilled") {
        newpro = new Promise((resolve, reject) => {
            let res = onFuiFiled(this.value);
            analysisPromise(newpro, res, resolve, reject);
        });
    }
    ...
    return newpro;
}

analysisPromise函数就是用来解析Promise中then函数的返回值的,在调用then函数中的状态函数返回结果值时都必须要进行处理。

Promise中的then函数是异步

现在,我们再来看Promise的一个例子:

let pro = new Promise((resolve, reject) => {
    resolve(123);
});
pro.then(data => {
    console.log(1);
});
console.log(2);

输出的结果是
2
1

so,这里先输出的是2,而then函数中的状态函数后输出1,说明同步代码先于then函数的异步代码执行

那这部分的代码我们该如何来实现呢?

采用setTimeout函数,给then函数中的每个匿名函数都加上setTimeout函数代码,就比如这样:

then(onFuiFiled, onRejected) {
    let newpro;
    if (this.status === "fulfilled") {
        newpro = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let res = onFuiFiled(this.value);
                    analysisPromise(newpro, res, resolve, reject);
                } catch (error) {
                    reject(error);
                }   
            });
        });
    }
    ...
    return newpro;
}

好了,关于Promise的大致实现就先分析到这里,希望对大家有帮助,文章中如有错误,欢迎指正

文章主要参考一下具体学习资源

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

推荐阅读更多精彩内容

  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,697评论 1 56
  • Promiese 简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,语法上说,Pr...
    雨飞飞雨阅读 3,348评论 0 19
  • Promise的含义:   Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和...
    呼呼哥阅读 2,161评论 0 16
  • 本文适用的读者 本文写给有一定Promise使用经验的人,如果你还没有使用过Promise,这篇文章可能不适合你,...
    HZ充电大喵阅读 7,293评论 6 19
  • 目录:Promise 的含义基本用法Promise.prototype.then()Promise.prototy...
    BluesCurry阅读 1,488评论 0 8