一步步写一个符合Promise/A+规范的库

Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果。

ES6 中采用了 Promise/A+ 规范,Promise 实现之前,当然要先了解 Promise/A+ 规范,规范地址https://promisesaplus.com/

我们根据 Promise/A+ 规范,可以写一个简单的Promise库。

每一步都尽量写的详细,所以代码很长很罗嗦。

1.实现Promise基本的方法

  • Promise是一个类,需要传递一个executor函数,函数里有两个参数resolve和reject,调用resolve代表成功,调用reject代表失败
  • Promise有三种状态:默认状态是等待状态pending,成功resolved,失败rejected

使用例子

let Promise = require('./Promise');
// Promise是一个类,需要传递一个executor函数,这个函数我们称之为执行函数,函数中有两个参数resolve和reject他们也是函数,调用resolve表示成功,调用reject表示失败
let promise = new Promise(function(resolve,reject){
    // 成功就不会再调用失败,默认状态是等待状态pending
    resolve('ok'); 
    reject('faild');
});
// then是原型上的一个方法接收两个参数分别是成功的回调和失败的回调
promise.then(function(data){// 调用resolve后会执行成功的回调,调用reject后会执行失败的回调
    console.log(data);
},function(err){
    console.log(err);
});

实现对应的Promise库代码

function Promise(executor) {  // Promise中需要接收一个执行函数executor
    let self = this;
    self.status = 'pending'; //默认是pending状态
    self.value = undefined; // 成功的原因
    self.reason = undefined; // 失败的原因
    function resolve(value) { // 调用resolve 会传入为什么成功
        if(self.status === 'pending'){ // 只有再pending才能转换成功态
            self.value = value; // 将成功的原因保存下来
            self.status = 'resolved'; // 状态改成成功态 
        }
    }
    function reject(reason) { // 调用reject会传入为什么失败
        if(self.status === 'pending'){
            self.reason = reason;
            self.status = 'rejected';
        }
    }
    try {
        executor(resolve, reject);// executor中需要传入resolve和reject
    } catch (e) {
        // 如果executor执行发生异常,表示当前的promise是失败态
        reject(e);
    }
}
// then中要传入成功的回调和失败的回调
Promise.prototype.then = function(onFufilled,onRejected){
    let self = this;
    // 如果要是成功就调用成功的回调,并将成功的值传入
    if(self.status === 'resolved'){
        onFufilled(self.value);
    }
    if(self.status === 'rejected'){
        onRejected(self.reason);
    }
}
module.exports = Promise

2.异步Promise

在new Promise时内部可以写异步代码,并且产生的实例可以then多次

  • 用两个数组存放成功和失败的回调,当调用的时候再执行

使用例子

let Promise = require('./Promise');
let promise = new Promise(function(resolve,reject){
    setTimeout(function(){
        resolve('ok');
    },1000)
});
// 当调用then时可能状态依然是pending状态,我们需要将then中的回调函数保留起来,当调用resolve或者reject时按照顺序执行
promise.then(function(data){
    console.log(data);
},function(err){
    console.log(err);
});
promise.then(function(data){
    console.log(data);
},function(err){
    console.log(err);
});

实现对应的Promise库代码

function Promise(executor) {  
     self.status = 'pending'; 
     self.value = undefined; 
     self.reason = undefined; 
+    self.onResolvedCallbacks = []; // 成功回调存放的地方
+    self.onRejectedCallbacks = [];// 失败回调存放的地方
     function resolve(value) { 
         if(self.status === 'pending'){ 
             self.value = value; 
             self.status = 'resolved'; 
+            // 依次执行成功的回调
+            self.onResolvedCallbacks.forEach(item=>item());
         }
     }
     function reject(reason) {
         if(self.status === 'pending'){
             self.reason = reason;
             self.status = 'rejected';
+            // 依次执行失败的回调
+            self.onRejectedCallbacks.forEach(item=>item());
         }
     }
}
Promise.prototype.then = function(onFufilled,onRejected){
     if(self.status === 'rejected'){
         onRejected(self.reason);
     }
+    if(self.status === 'pending'){
+        // 如果是等待态,就将成功和失败的回调放到数组中
+        self.onResolvedCallbacks.push(function(){
+            onFufilled(self.value);
+        });
+        self.onRejectedCallbacks.push(function(){
+            onRejected(self.reason);
+        });
+    }
 }

3.Promise链式调用

  • 如果当前promise已经进入成功的回调,回调中发生了异常返回this的话,那么当前的promise的状态无法更改到失败台!所以promise实现链式调用,返回的并不是this而是一个新的promise。

  • 执行回调中

    • 如果返回的是一个普通的值,会将结果传入下一次then的成功回调中
    • 如果发生错误会被下一次then的失败回调捕获
    • 如果返回的是promise看这个promise是成功还是失败,对应调用下一次的then

    所以写一个resolvePromise方法,这是promise中最重要的方法,用来解析then返回的结果

  • 有些人可能成功失败同时调用,如果两个都调用,用第一个调用的,不允许同时调用。

使用例子

promise.then(function(data){
    throw Error('出错了');// 当前promise已经成功了,成功就不会再失败
    return 'renee' 
}).then(null,function(err){ // 如果返回的是同一个promise那么还怎么走向失败呢?所以必须要返回一个新的promise
    console.log(err);
})

实现对应的Promise库代码

 Promise.prototype.then = function(onFufilled,onRejected){
     let self = this;
+    let promise2; // promise2为then调用后返回的新promise
     // 如果要是成功就调用成功的回调,并将成功的值传入
     if(self.status === 'resolved'){
-        onFufilled(self.value);
+        promise2 = new Promise(function(resolve,reject){
+            try{
+                // 执行时有异常发生,需要将promise2的状态置为失败态
+                let x = onFufilled(self.value); 
+                // x为返回的结果
+                // 写一个方法resolvePromise,是对当前返回值进行解析,通过解析让promise2的状态转化成成功态还是失败态
+                resolvePromise(promise2,x,resolve,reject);
+            }catch(e){
+                reject(e);
+            }
+        })
     }
     if(self.status === 'rejected'){
-        onRejected(self.reason);
+        promise2 = new Promise(function(resolve,reject){
+            try{
+                let x = onRejected(self.reason);
+                resolvePromise(promise2,x,resolve,reject);
+            }catch(e){
+                reject(e)
+            }
+        })
     }
     if(self.status === 'pending'){
+       promise2 = new Promise(function(resolve,reject){
         self.onResolvedCallbacks.push(function(){
-            onFufilled(self.value);
+                try{
+                    let x = onFufilled(self.value);
+                    resolvePromise(promise2,x,resolve,reject)
+                }catch(e){
+                    reject(e);
+                }
+            })
         });
         self.onRejectedCallbacks.push(function(){
-            onRejected(self.reason);
+                try{
+                    let x = onRejected(self.reason);
+                    resolvePromise(promise2,x,resolve,reject)
+                }catch(e){
+                    reject(e);
+                }
         });
+       })
     }
+    return promise2;
 }
function resolvePromise(promise2, x, resolve, reject) {
    //x是返回的结果,如果promise和then中返回的promise是同一个,是不科学的,要报错
    if(promise2===x){
        return reject(new Error('循环引用'))
    }
    if(x!==null&&(typeof x === 'object'|| typeof x === 'function')){
        let called; //表示是否调用过成功或者失败
        try{
            let then=x.then;
            //如果then是函数,说明是promise,我们要让promise执行
            if(typeof then==='function'){
                then.call(x,function(y){
                    if(called)return; //如果调用过直接return
                    called=true;
                    //如果resolve的结果依旧是promise那就继续解析
                },function(err){
                    if(called) return;
                    called=true;
                    reject(err)
                })
            }else{//如果不是函数,x是一个普通的对象,直接成功即可
                resolve(x)
            }
        }catch(e){
            if(called) return;
            called=true;
            reject(e);
        }
    }else{
        //是普通值直接调用成功
        resolve(x);
    }
}

4.值的穿透

  • 在规范中定义then函数可以不传参,不传参默认会将成功的结果和失败的结果继续向下传递

使用例子

promise.then().then().then(function(data){
  console.log(data)
})

实现对应的Promise库代码

Promise.prototype.then = function (onFufilled, onRejected) {
  //失败和成功默认不传给一个函数
+    onFufilled = typeof onFufilled === 'function'?onFufilled:function(value){
+        return value
+    }
+    onRejected = typeof onRejected === 'function'?onRejected:function(err){
+        throw err
+    }

5.测试

另外可以通过安装一个插件来对实现的promise进行规范测试。

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

推荐阅读更多精彩内容