学着写一个简单的promise

定义

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

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

说在前面

这是一个简版非规范的Promise实现,其中前置知识点比较重要,理解了前置知识点的内容,也就了解了Promise最基本的原理。

前置知识

这小节的核心是:把函数名当变量用。

以下三点理解之后,就能够实现简单的Promise了。

类型为Function的变量,可以像函数一样调用。

举个栗子

let myFn = function () {};
myFn(); 

和下面的代码效果是一样的

function myFn() {
    // code here...
}
myFn();

函数数组,即函数里的每一个元素都是函数,可以用遍历调用。

let fnArray = [];
let fn1 = () => { console.log(1) };
let fn2 = () => { console.log(2) };
fnArray.push(fn1,fn2);
fnArray.forEach(cb => cb());

运行结果是

1
2

函数可以当作参数传递

function showYouFn(fn1, fn2) {
    // 调用
    fn1();
    fn2();
}

let myFn1 = () => { console.log(3); };
let myFn2 = () => { console.log(4); };

showYouFn(myFn1, myFn2);

回想一下:把函数名当变量用。

实现简单模型

下面是一个简陋得不像Promise的Promise实现

function MyPromise(fn) {
    function resolve(value) {
        console.log(value);
    }
    function reject(value) {
        console.log(value);
    }
    
    fn(resolve, reject);
}

现在你就可以用上自定义的Promise了

new MyPromise((resolve, reject) => {
    setTimeout(()=>{
        resolve('你将看到两秒后的我');
    }, 2000);
});

将会在2秒后输出

你将看到两秒后的我

解释一下整体代码:

MyPromise 中的参数 fn是需要用户传入的自定义函数(该函数需要接收两个参数)。

MyPromise 中的内部还有两个函数resolvereject,其中 resolve 表示用户的异步任务成功时应该调用的函数,reject 表示用户的异步任务失败时该调用的函数。

那用户如何调用 resolvereject 呢?

很简单,把两个函数当作 fn的参数传递出去即可。

所以 MyPromise 内部在调用 fn 时会把 resolvereject当作参数传递给 fn。

然后用户在自定义函数内调用 resolvereject 来通知 MyPromise 异步任务已经执行完了。

通过上面的代码可以发现一个问题,我们不知道Promise的异步任务进行到哪一步了、是成功还是失败了。

所以增加三个状态用来标识一个Promise的异步任务进行到何种程度了。

pending、resolved、rejected 分别表示 执行中、已完成、已失败

然后通过观察用户调用的是 resolve 还是 reject 可以判断当前Promise的状态。
那么会有三种情况:

  • 在用户调用 resolve 或 reject 之前状态是 pending
  • 用户调用 resolve 时,状态将变为 resolved
  • 用户调用 reject 时,状态将变为 rejected

下面进行代码的改造,定义了三个常量表示状态以及一个变量 state 用来存储当前状态。
并且当 resolve 被调用时将 state 修改为 resolved 。

    const PEDNING="pending";//执行状态
    const RESOLVED='resolved';//以完成;
    const REJECTED='rejected';//以失败

    function MyPromise(fn){
        const that=this
        //初始状态为执行中,pending
        this.state=PEDNING;

        function resolve(value){
            console.log(value)
            that.state=RESOLVED;
        }
        function reject(err){
            console.log(err)
            that.state=REJECTED;
        }
        fn(resolve,reject);
    }

OK,现在已经能知道Promise当前所处的状态了,但是任务完了得拿到结果吧,MyPromiseresolve 被调用,那也只是MyPromise知道任务完成了,用户还不知道呢。
所以我们需要回调函数告诉用户,是的,其实就是回调函数。
这时候就轮到 then 方法出场了,用户通过then方法传入回调函数, MyPromise 将在成功调用 resolve 时调用用户传入的回调函数。
开始改造代码,MyPromise 内部需要变量存储回调函数,then 方法的作用就是将用户传入的回调函数赋予 MyPromise 内的变量。
所以 then 方法长这样,接收两个参数,一个是成功时的回调函数,一个是失败时的回调函数

        const PEDNING="pending";//执行状态
        const RESOLVED='resolved';//以完成;
        const REJECTED='rejected';//以失败

        function MyPromise(fn){
            const that=this
            //初始状态为执行中,pending
            this.state=PEDNING;
            //两个储存回调函数的变量
            this.resolvedCallback;
            this.rejectedCallback;

            function resolve(value){
                that.state=RESOLVED;
                that.resolvedCallback && that.resolvedCallback(value); 
            }
            function reject(err){
                that.state=REJECTED;
                that.rejectedCallback && that.rejectedCallback(err);
            }
            fn(resolve,reject);
        }

        MyPromise.prototype.then=function(onFulfilled,onRejected){
            this.resolvedCallback = onFulfilled;
            this.rejectedCallback = onRejected;
        }

是的,一个简版Promise几乎大功告成,让我们再试试在浏览器执行如下代码(注意我删除了 resolve 和 reject 里的console语句);咱们来使用一下:

    (function(){
        
        const PEDNING="pending";//执行状态
        const RESOLVED='resolved';//以完成;
        const REJECTED='rejected';//以失败

        function MyPromise(fn){
            const that=this
            //初始状态为执行中,pending
            this.state=PEDNING;
            //两个储存回调函数的变量
            this.resolvedCallback;
            this.rejectedCallback;

            function resolve(value){
                that.state=RESOLVED;
                that.resolvedCallback && that.resolvedCallback(value); 
            }
            function reject(err){
                that.state=REJECTED;
                that.rejectedCallback && that.rejectedCallback(err);
            }
            fn(resolve,reject);
        }

        MyPromise.prototype.then=function(onFulfilled,onRejected){
            this.resolvedCallback = onFulfilled;
            this.rejectedCallback = onRejected;
        }

        new MyPromise((resolve,reject)=>{
            setTimeout(()=>{
                resolve('我是结果')
            },4000);
        }).then((value)=>{
            console.log(value)
        })
    })()

通过匿名函数和函数自执行,形成局部作用域,保护里面的变量;

上面的代码,用法上已经和Promise长得差不多了,但是如果我们多次调用 then 方法呢?
是的,只有最后一个 then 方法里的回调函数能执行,这当然没法满足我们的需要。
于是,将两个回调函数改成函数数组(请回想一下前置知识),并在状态更改时遍历调用回调函数。
改造后的代码如下:

    (function(){

    const PEDNING="pending";//执行状态
        const RESOLVED='resolved';//以完成;
        const REJECTED='rejected';//以失败

        function MyPromise(fn){
            const that=this
            //初始状态为执行中,pending
            this.state=PEDNING;
            //两个储存回调函数的变量,注意,变成了数组
            this.resolvedCallbackList=[];
            this.rejectedCallbackList=[];

            function resolve(value){
                that.state=RESOLVED;
                that.resolvedCallbackList && that.resolvedCallbackList.forEach(cbFn =>cbFn(value)); 
            }
            function reject(err){
                that.state=REJECTED;
                that.rejectedCallbackList && that.rejectedCallbackList.forEach(cbFn =>cbFn(err)); 
            }
            fn(resolve,reject);
        }

        MyPromise.prototype.then=function(onFulfilled,onRejected){
            this.resolvedCallbackList.push(onFulfilled);
            this.rejectedCallbackList.push(onRejected);
             // 这里是为了链式调用,所以要返回this,即myPromise.then().then().then()...
            return this
        }

        new MyPromise((resolve,reject)=>{
            setTimeout(()=>{
                resolve('经过5秒出现')
            },5000)
        }).then((val)=>{
            console.log(val+'第一次出现的value')
        }).then((val)=>{
            console.log(val+'第二次出现的value')
        })
    })()

上面已经是简版Promise的实现了。
但是我们还可以更完善一点,增强 MyPromise 的健壮性。
例如,若用户自定义函数在执行过程中发生了错误,会中断程序的执行,于是我们增加try...catch...语句,并在发生错误时主动执行reject函数告知用户。

try {
    fn(resolve, reject);
} catch (e) {
    reject(e);
}

又或者,对参数进行校验,状态进行判断等,以 then为例,若用户传入的参数不是函数呢? 或者Promise的状态已经时rejected或resolved,此时调用then呢?

改造 then 后代码如下:

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

推荐阅读更多精彩内容

  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,673评论 1 56
  • 你不知道JS:异步 第三章:Promises 在第二章,我们指出了采用回调来表达异步和管理并发时的两种主要不足:缺...
    purple_force阅读 2,033评论 0 4
  • JavaScript里通常不建议阻塞主程序,尤其是一些代价比较昂贵的操作,如查找数据库,下载文件等操作,应该用异步...
    张歆琳阅读 2,731评论 0 12
  • //本文内容起初摘抄于 阮一峰 作者的译文,用于记录和学习,建议观者移步于原文 概念: 所谓的Promise,...
    曾经过往阅读 1,220评论 0 7
  • 原文地址:http://es6.ruanyifeng.com/#docs/promise Promise 的含义 ...
    AI云栈阅读 858评论 0 7