promise这个词语,如果从英文的词义上理解大意是‘承诺’的意思,因此你可以认为这个东东要做的事情就是对js代码进行一层封装,保证接下来可以在后面的逻辑中,实时的获取成功或失败的结果,这也正是promise应运而生的意义。

基本用法
const p = new Promise(function(resolve, reject) {...})
我们看下这个promise里面都有啥?

promise组成结构
很明显promise的原型上绑定了三个实例方法:分别是

  • then
  • catch
  • finally

接下来我们分别看看三个实例方法的具体用法,这里我只做简单的介绍使用
const p1 = new Promise((res, reject) => {
    res('成功了');
});

p1.then(function(res) {
    console.log(res, 'success'); // 打印成功了
}, function(err) {
    console.log(err, 'fail')
});

const p2 = new Promise((res, reject) => {
    reject('失败了');
});

p2.then(function(res) {
    console.log(res, 'success');
}, function(err) {
    console.log(err, 'fail') // 打印失败了
});
这是ES2015 promise的常规用法,当然我们也可以写成连缀的方式;像下面这样
new Promise((res, reject) => {
    ...
})
.then(onFulFilled = () => {})
.catch(onRejected = () => {})
.finally(onFinal = () => {})

不管promise最终结果(执行或者成功或者失败)都会走到finally这个钩子函数中。
如果我们需要在异步操作中执行promise的结果,该怎么做呢?该方案可行吗?带着问题看下面代码:
const p3 = new Promise((res, reject) => {
    setTimeout(() => {
        res('异步调用promise成功了');
    }, 2000);
});

p3.then(res => {
    console.log(res, '===async_promise');//2秒后 打印 异步调用promise成功了
}).catch(err => {
//这里不会走到这里,因为promise调用resolve成功的钩子函数
})
我们从上面的demo中发现 如果promise的执行结果结束了,最终只会是成功或者失败的一种,不会存在并发的结果,
并且也支持异步调用 +1很赞
像下面这种用法我们可以依次打印 0,1,2,3,4,5
const arr = []
const output = i => new Promise(res => {
  setTimeout(()=>{
    console.log(i)
    res()
  }, 1000*i)
})
for (let i=0; i<5; i++) {
  arr.push(output(i))
}
Promise.all(arr).then(() =>  console.log(5));

如果你熟悉es6(ES2016下面都这么简写了)async&await的使用方法话,还可以这么优雅的使用

const sleep = () => new Promise (res => setTimeout(res, 1000))
(async function () {
  for (let i=0; i<5; i++) {
    await sleep()
    console.log(i)
  }
})();

依然可以实现上面的要求;是不是现在有点喜欢上了promise了。
说到这里我觉得,是时候了解了解promise内部实现原理了,只有内部原理搞懂了,那么我们就可以游刃有余愉快的撸代码了。。。。。。废话不多说直接开干。
所以第一步 (傻傻想不起来该如何下手啊,看我们之前promise的使用方法开始下手);
既然有new Promise(function(res,rej) {});

那么就该有高配版 new MyPromise(function(res, rej) {});哈哈这个就是我们接下来要定义的类名。

继续!

class MyPromise {}  ....完成了,别逗了,继续童鞋们。


这个类要有自己的一点点东西吧,比如上文说的一些实例方法吧 
then, catch, finally等等吧。。。。

继续

于是应该是

class MyPromise {

    constructor() {

    }

    then(resolve = () => {}, rej = () => {}) {

    }

}

好了不卖关子了,直接一步步撸代码吧,我备注下,只要你认真的看下去应该so easy才对,因为你很优秀!

class MyPromise {

    constructor(fn) {
        this.state = 'PEDDING'; //我们定义一个状态 表示       promise的进度如何, 初始化就正在进行, 还有成功和失败两个等着
        this.value = undefined; //要接收回调的值,初始化就给个undefined吧
        
        //没错最重要的就是这个fn我们实例传进去的函数,初始化要调用一次
        fn(this.resolve.bind(this), this.reject.bind(this));
    }
    
   
    resolve(value) {
        if (this.state === "PEDDING") {
            this.state = "RESOLVED";
            this.value = value;
        }
    }
    
    reject(value) {
        if (this.state === "PEDDING") {
            this.state = "REJECTED";
            this.value = value;
        }
    }

    then(resolve = () => {}, reject = () => {}) {
       //resolve,reject是我们自己传递的两个函数参数
        if (this.state === "RESOLVED") {
            resolve(this.value);
        }
        if (this.state === "REJECTED") {
            reject(this.value);
        }

    }

}

大功告成。怎么说要测试下吧。

测试自定义的promise

一个简单的promise基本雏形出来了,是不是很有成就感!
顺便来个异步的调用试试看。。。。来就来,走一波代码。
var asyncP = new MyPromise((res, rej) => setTimeout(() => res(123), 2000));
asyncP.then(r => console.log(r, '====r'));

自定义异步promise调用

神马情况,居然undefined,头疼。。。。。。

好吧,肯定是有问题的喽,大家可以对上面的代码打个断点试下,原来是state的状态一直是‘PEDDING’状态,所以代码就不会进入成功或者失败的钩子函数中。看来我们需要有一个储存机构来管理这些不确定状态的代码啊,以备后续用到啊。起码我异步调用可以执行吧。废话不多说,直接上代码。
功能相对比较齐全的自定义promise刚出锅。。。。。


class MyPromise {

    constructor(fn) {
    
    
        this.resolvedCallbacks = [];//缓存成功的钩子函数
        this.rejectedCallbacks = [];//缓存失败的钩子函数

        this.state = 'PEDDING'; //我们定义一个状态 表示       promise的进度如何, 初始化就正在进行, 还有成功和失败两个等着
        this.value = undefined; //要接收回调的值,初始化就给个undefined吧
        
        //没错最重要的就是这个fn我们实例传进去的函数,初始化要调用一次
        fn(this.resolve.bind(this), this.reject.bind(this));
    }
    
   
    resolve(value) {
        if (this.state === "PEDDING") {
            this.state = "RESOLVED";
            this.value = value;
            
            //缓存中的成功的钩子函数走起来
            this.resolvedCallbacks.forEach(cb => cb());
        }
    }
    
    reject(value) {
        if (this.state === "PEDDING") {
            this.state = "REJECTED";
            this.value = value;
            
            //缓存中的失败的钩子函数走起来
            this.rejectedCallbacks.forEach(cb => cb());
        }
    }

    then(resolve = () => {}, reject = () => {}) {
    
       //resolve,reject是我们自己传递的两个函数参数
       
      
        if (this.state === "PEDDING") {
        //如果用箭头函数可以省略this的重赋值
            var that  = this;
            this.resolvedCallbacks.push(function() {
                resolve(that.value);
            });
            this.rejectedCallbacks.push(function() {
                reject(that.value);
            });
        }

        if (this.state === "RESOLVED") {
            resolve(this.value);
        }
        if (this.state === "REJECTED") {
            reject(this.value);
        }

    }

}

应该可以秀一波操作了,测试走起,还是之前的异步调用

自定义再次调用异步promise

很明显这次没问题了。2秒后钩子函数正确执行。。。该去喝口水了,口渴。

ES5 promise有一点不是很完美的解决方案,因为假如有一种情况我们需要或者多个promise的最终结果,不管失败还是成功。如果这样该怎么去解决呢?
ES6中针对这一部分有了更好的解决方案。。。我们下一期再谈谈该如何更加优雅的使用promise

chriswork
0 声望0 粉丝

前端深似海,一朝白了头