Promise

一、Promise 相关概念

学习参考 廖雪峰 的博客:

在JavaScript的世界中,所有代码都是单线程执行的。

由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行,异步执行可以用回调函数实现:

function callback() {
    console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000); // 1秒钟后调用callback函数
console.log('after setTimeout()');

观察上述代码执行,在Chrome的控制台输出可以看到:

before setTimeout()
after setTimeout()
(等待1秒后)
Done

即:异步操作会在将来的某个时间点触发一个函数调用。

再来一个例子:

回调函数使用频率很高的不能不提 ajax 请求,由于网速等问题,客户端发起一个ajax请求后,得到响应的时间是不固定的。

在ajax的原生实现中,利用了onreadystatechange事件,当该事件触发并且符合一定条件时,才能拿到我们想要的数据,之后我们才能开始处理数据。

var  xhr = new XMLHttpRequest();
xhr.open('get','http://localhost:3000/api/one',true);
xhr.send();
xhr.onreadystatechange = function () {
    console.log(xhr.readyState);
    if(xhr.readyState === 4){  // 代表服务器已经给了响应, 不代表响应成功
        if(xhr.status === 200){
            console.log(xhr.response);
        }
    }
}

如果这个时候,我们还需要做另外一个ajax请求,这个新的ajax请求中的一个参数,得从上一个ajax请求中获取,这个时候,得在上一个里面继续进行一个 ajax 请求:

var xhr = new XMLHttpRequest();
xhr.open('get','http://localhost:3000/api/one',true);
xhr.send();
xhr.onreadystatechange = function () {
    console.log(xhr.readyState);
    if(xhr.readyState === 4){  // 代表服务器已经给了响应, 不代表响应成功
        if(xhr.status === 200){
           // 在这里进行新的 ajax 请求
            var xhr2 = new XMLHttpRequest();
            xhr2.open('get','http://localhost:3000/api/one',true);
            xhr2.send();
            xhr.onreadystatechange = function () {.........}
        }
    }
}

好,那么如果再来一个呢,这个就是 回调地狱了,为了解决 回调地狱 的问题,让代码更加可读,于是有了 Promise

“君子一诺千金”,这种“承诺将来会执行”的对象在JavaScript中称为Promise对象。



二、Promise 原理

Promise对象有三种状态:

  • pending: 等待中,或者进行中,表示还没有得到结果
  • resolved(Fulfilled): 已经完成,表示得到了我们想要的结果,可以继续往下执行
  • rejected: 也表示得到结果,但是由于结果并非我们所愿,因此拒绝执行

三种状态不受外界影响,状态只能从 pending 变为 resolved 或者 rejected.

function fn(num){
    return new Promise(function (resolve,reject) {
        if(typeof num == 'number'){
            resolve();
        }else{
            reject();
        }
    }).then(function () {
        console.log('It is a number');
    },function () {
        console.log('It is not a number');
    })
}

fn(1);
fn('hello');
// 打印结果:
/*
It is a number
It is not a number
*/
  • resolve() 和 reject() 的作用就是将状态修改为 resolved 和 rejected

  • .then() 里面两个 function() 参数,第一个是满足条件的resolve,第二个是不满足条件的 reject.分别根据获取到的状态去执行相应的函数


then 方法执行完后会返回一个 promise对象,所以可以进行.then的链式调用,这样就解决了回调地狱的问题

function fn(num) {
    return new Promise(function (resolve,reject) {
        if(typeof num == 'number'){
            resolve();
        }else{
            reject();
        }
    })
    .then(function () {
        console.log('It is a number');
    })
    .then(null,function () {
        console.log('It is not a number');
    })
}

fn(123);
fn('hello');
/*
执行结果:
It is a number
It is not a number
*/



三、关于Promise的数据传递

var fn = function (num) {
    return new Promise(function (resolve,reject) {
        if(typeof num == 'number'){
            resolve(num)
        }else {
            reject('not a number')
        }
    })
}


fn(1).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:1
second: 2
third: 3
* */

多个参数传递:

new Promise((resolve, reject)=>{
    resolve(1, 2, 232, 212);
}).then((data1, data2, data3, data4)=>{
    console.log(data1, data2, data3, data4);
});



四、错误处理

关于错误处理:在使用 Promise 做异步流程控制的时候,关于异常的处理可以通过在最后一个 then 之后设置一个 catch,然后指定一个失败处理函数, 该函数可以捕获前面所有的 Promise 对象本身以及 then 内部的任务错误,当前面任何一个发生异常,直接进入 catch,后续所有的 Promise 包括 then 不再执行

const fs = require('fs');

// 1. 创建一个Promise对象 (一经创建, 立马执行)
readFile(__dirname + '/data/a.txt', 'utf8')
    .then((data) => {
        console.log(data.toString());
        return readFile(__dirname + '/data/b.txt', 'utf8');
    })
    .then((data) => {
        console.log(data.toString());
        return readFile(__dirname + '/data/c.txt', 'utf8');
    })
    .then((data) => {
        console.log(data.toString());
    }).catch(err => {
        console.log(err);
});


function readFile(...args) {
    return new Promise((resolve, reject) => {
        fs.readFile(...args, (err, data) => {
            if (err) {
                reject(err);
            }
            resolve(data);
        })
    })
}



五、封装ajax

主流框架的ajax一般都是 用Promise封装的。

Promise 封装ajax 示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    function xhr(options) {
        return new Promise(function (resolve, reject) {
            var xhr = new XMLHttpRequest();

            xhr.open(options.type || 'get', options.url,true);
            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(xhr.responseText);
                    }

                }
            };
            xhr.send();
        })
    }

    xhr({
        url:'http://ip.taobao.com/service/getIpInfo.php?ip=117.89.35.58',
        type:'get',
        data:''
    }).then((data)=>{
        console.log(data);
    },function (err) {
        console.log(err);
    })


</script>
</body>
</html>

Promise封装ajax,待补充一个完整的,上面的这个现在只处理了 get 方式的


六、Promise.all

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

var p1 = new Promise(function (resolve, reject) {
    resolve('111');
});

var p2 = new Promise(function (resolve,reject) {
    resolve('222');
});

var p3 = new Promise(function (resolve,reject) {
   reject('333')
});

Promise.all([p1,p2]).then(function (data) {
    console.log(data);    // 打印 [ '111', '222' ]
}).catch(function (err) {
    console.log(err);
});

Promise.all([p1,p2,p3]).then(function (data) {
    console.log(data);
}).catch(function (err) {
    console.log(err);  // 打印:333
})
  • Promise.all 得到所有成功的结果的顺序,与传入的Promise对象的数组顺序保持一致。如上面的代码,即使是p2的结果先出来,顺序也不会变。
  • 如果我们下一步的动作,需要等待其他的两个结果先出来才能判读是否要往下进行,用Promise.all 来解决。

七、Promise.race

race :种族,赛跑

Promise.race([p1,p2,p3]), 里面的三个任务赛跑,谁先获取到结果谁就跑的快,不管返回的结果是成功还是失败。

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('1000秒--success');
        console.log(1111);
    },1000)
})

let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('500秒--failed')
        console.log(2222);
    }, 500)
})


Promise.race([p1, p2]).then((result) => {
    console.log('resolve:'+result)
},function (err) {
    console.log('reject:'+err);
}).catch((error) => {
    console.log('catch:'+error)  // 打开的是 'failed'
});

/*
执行结果:
2222
reject:500秒--failed
1111
* */
  • 只要是在数据组里的,都会执行
  • 只返回了跑得快的任务的结果
原文地址:https://www.cnblogs.com/friday69/p/10402795.html