登录 ×

梨花寨前端技术交流平台!

html,css,js,html5,css3等

JS Promise的实现原理

2018-10-08 16:14:00 | 浏览量:42 前沿信息 es6
众所周知的,Javascript是一种单线程的语言,所有的代码必须按照所谓的“自上而下”的顺序来执行。本特性带来的问题就是,一些将来的、未知的操作,必须异步实现。本文将讨论一个比较常见的异步解决方案——Promise,Promise的应用已经极其广泛。
Promise解决的问题
当一个异步任务的执行需要依赖另一个异步任务的结果时,我们一般会将两个异步任务嵌套起来,这种情况发生一两次还可以忍,但是发生很多次之后,你的代码就会变成这个熊样:
async1(function(){ 
    async2(function(){ 
        async3(function( 
            async4(funciton(){ 
              //... 
            }); 
        )); 
    }); 
});
这就是所谓的回调地狱,代码层层嵌套,环环相扣,很明显,逻辑稍微复杂一些,这样的程序就会变得难以维护。
在实际的使用当中,有非常多的应用场景我们不能立即知道应该如何继续往下执行。最重要也是最主要的一个场景就是ajax请求,由于需要网络传输,这个需要等待,结果出来了之后才知道怎么样继续下去。
Promise的标准化,一定程度上解决了JavaScript的流程操作问题。
当然,除了回调地狱之外,还有一个非常重要的需求:为了我们的代码更加具有可读性和可维护性,我们需要将数据请求与数据处理明确的区分开来。
function want() {
    console.log('这是你想要执行的代码');
}
function fn(want) {
    console.log('这里表示执行了一大堆各种代码');
    // 返回Promise对象
    return new Promise(function(resolve, reject) {
        if (typeof want == 'function') {
            resolve(want);
        } else {
            reject('TypeError: '+ want +'不是一个函数')
        }
    })
}
fn(want).then(function(want) {
    want();
})
fn('1234').catch(function(err) {
    console.log(err);
}) 
看上去变得更加复杂了。可是代码变得更加健壮,处理了错误输入的情况。
介绍一下Promsie的基础知识
一、 Promise对象有三种状态,他们分别是:
pending: 等待中,或者进行中,表示还没有得到结果
resolved(Fulfilled): 已经完成,表示得到了我们想要的结果,可以继续往下执行
rejected: 也表示得到结果,但是由于结果并非我们所愿,因此拒绝执行
这三种状态不受外界影响,而且状态只能从pending改变为resolved或者rejected,并且不可逆。在Promise对象的构造函数中,将一个函数作为第一个参数。而这个函数,就是用来处理Promise的状态变化。
new Promise(function(resolve, reject) {
    if(true) { resolve() };
    if(false) { reject() };
}) 
上面的resolve和reject都为一个函数,他们的作用分别是将状态修改为resolved和rejected。
二、 Promise对象中的then方法,可以接收构造函数中处理的状态变化,并分别对应执行。then方法有2个参数,第一个函数接收resolved状态的执行,第二个参数接收reject状态的执行。
function fn(num) {
    return new Promise(function(resolve, reject) {
        if (typeof num == 'number') {
            resolve();
        } else {
            reject();
        }
    }).then(function() {
        console.log('参数是一个number值');
    }, function() {
        console.log('参数不是一个number值');
    })
}
fn('hahha');
fn(1234);
如果异步操作获得了我们想要的结果,那我们将调用resolve函数,在then的第一个作为参数的匿名函数中可以获取数据,如果我们得到了错误的结果,调用reject函数,在then函数的第二个作为参数的匿名函数中获取错误处理数据。 这样,一个次完整的Promise调用就结束了。
对于Promise的then()方法,then总是会返回一个Promise实例,因此你可以一直调用then,形如run().then().then().then().then().then().....
在一个then()方法调用异步处理成功的状态时,你既可以return一个确定的“值”,也可以再次返回一个Promise实例,
当返回的是一个确切的值的时候,then会将这个确切的值传入一个默认的Promise实例,并且这个Promise实例会立即置为fulfilled状态,以供接下来的then方法里使用。这也是解决回调地狱的主要方式。
function fn(num) {
    return new Promise(function(resolve, reject) {
        if (typeof num == 'number') {
            resolve();
        } else {
            reject();
        }
    })
    .then(function() {
        console.log('参数是一个number值');
    })
    .then(null, function() {
        console.log('参数不是一个number值');
    })
}
fn('hahha');
fn(1234); 
then(null, function() {}) 就等同于catch(function() {})
三、Promise中的数据传递
var fn = function(num) {
    return new Promise(function(resolve, reject) {
        if (typeof num == 'number') {
            resolve(num);
        } else {
            reject('TypeError');
        }
    })
}
fn(2).then(function(num) {
    console.log('first: ' + num);
    return num + 1;
})
.then(function(num) {
    console.log('second: ' + num);
    return num + 1;
})
.then(function(num) {
    console.log('third: ' + num);
    return num + 1;
});
// 输出结果
first: 2
second: 3
third: 4 
OK,了解了这些基础知识之后,我们再回过头,利用Promise的知识,对最开始的ajax的例子进行一个简单的封装。
var url = 'https://xxx.com/zz';
// 封装一个get请求的方法
function getJSON(url) {
    return new Promise(function(resolve, reject) {
        var XHR = new XMLHttpRequest();
        XHR.open('GET', url, true);
        XHR.send();
        XHR.onreadystatechange = function() {
            if (XHR.readyState == 4) {
                if (XHR.status == 200) {
                    try {
                        var response = JSON.parse(XHR.responseText);
                        resolve(response);
                    } catch (e) {
                        reject(e);
                    }
                } else {
                    reject(new Error(XHR.statusText));
                }
            }
        }
    })
}
getJSON(url).then(resp => console.log(resp));
为了健壮性,处理了很多可能出现的异常,总之,就是正确的返回结果,就resolve一下,错误的返回结果,就reject一下。并且利用上面的参数传递的方式,将正确结果或者错误信息通过他们的参数传递出来。
四、Promise.all
当有一个ajax请求,它的参数需要另外2个甚至更多请求都有返回结果之后才能确定,那么这个时候,就需要用到Promise.all来帮助我们应对这个场景。
Promise.all接收一个Promise对象组成的数组作为参数,当这个数组所有的Promise对象状态都变成resolved或者rejected的时候,它才会去调用then方法。
var url = 'https://xxx.com/zz';
var url1 = 'https://xxx.com/zz1';
function renderAll() {
    return Promise.all([getJSON(url), getJSON(url1)]);
}
renderAll().then(function(value) {
    // 建议大家在浏览器中看看这里的value值
    console.log(value);
}) 
五、 Promise.race
与Promise.all相似的是,Promise.race都是以一个Promise对象组成的数组作为参数,不同的是,只要当数组中的其中一个Promsie状态变成resolved或者rejected时,就可以调用.then方法了。而传递给then方法的值也会有所不同,大家可以再浏览器中运行下面的例子与上面的例子进行对比。
function renderRace() {
    return Promise.race([getJSON(url), getJSON(url1)]);
}
renderRace().then(function(value) {
    console.log(value);
}) 
需求及问题提交:

点击图片更换数字

0.6526s