JavaScript(JS) Promise介绍及使用

Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。本文主要介绍JavaScript(JS)中Promise,以及相关的使用方法,以及示例代码。

1、 Promise简介

Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。

Promise对象有以下两个特点:

1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就固定,会一直保持这个结果。就算再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同。

注意:事件的特点是,如果错过了它,再去监听,是得不到结果的。 有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。 Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 如果某些事件不断地反复发生,一般来说,使用stream模式是比部署Promise更好的选择。

Promise 浏览器支持的情况:

Promise 是 ES6 新增加的,所以一些旧的浏览器并不支持,苹果的 Safari 10 和 Windows 的 Edge 14 版本以上浏览器才开始支持 ES6 特性。

具体支持情况:

Chrome 58、Edge 14、Firefox 54、Safari 10、Opera 55

2、Promise 的使用

Promise 构造函数只有一个参数,是一个函数,这个函数在构造之后会直接被异步运行,所以我们称之为起始函数。起始函数包含两个参数 resolvereject

例如,

new Promise(function (resolve, reject) {
var x = 0;
var y = 1;
if (y == 0) reject("除数不能为零");
else resolve(x / y);
}).then(function (value) {
console.log("x / y = " + value);
}).catch(function (err) {
console.log(err);
}).finally(function () {
console.log("End");
});

输出结果:

x / y = 0
End

注意:

resolvereject 都是函数,其中调用 resolve 代表一切正常,reject 是出现异常时所调用的。Promise 类有 .then().catch().finally() 三个方法,这三个方法的参数都是一个函数,.then() 可以将参数中的函数添加到当前 Promise 的正常执行队列,.catch() 则是设定 Promise 的异常处理序列,.finally() 是在 Promise 执行的最后一定会执行的序列。 .then() 传入的函数会按顺序依次执行,有任何异常都会直接跳到 catch 序列。

例如,

// 第一步:model层的接口封装
const promise = new Promise((resolve, reject) => {
    // 这里做异步任务(比如ajax 请求接口。这里暂时用定时器代替)
    setTimeout(function() {
        var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的数据
        if (data.retCode == 0) {
            // 接口请求成功时调用
            resolve(data);
        } else {
            // 接口请求失败时调用
            reject({ retCode: -1, msg: 'network error' });
        }
    }, 100);
});
// 第二步:业务层的接口调用。这里的 data 就是 从 resolve 和 reject 传过来的,也就是从接口拿到的数据
promise.then(data => {
    // 从 resolve 获取正常结果
    console.log(data);
}).catch(data => {
    // 从 reject 获取异常结果
    console.log(data);
});

Promise 处理 多次 Ajax 请求:

/*
  基于Promise发送Ajax请求
*/
function queryData(url) {
    var promise = new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState != 4) return;
            if (xhr.readyState == 4 && xhr.status == 200) {
                // 处理正常情况
                resolve(xhr.responseText); // xhr.responseText 是从接口拿到的数据
            } else {
                // 处理异常情况
                reject('接口请求失败');
            }
        };
        xhr.responseType = 'json'; // 设置返回的数据类型
        xhr.open('get', url);
        xhr.send(null); // 请求接口
    });
    return promise;
}
// 发送多个ajax请求并且保证顺序
queryData('http://localhost:3000/api1')
    .then(
        data1 => {
            console.log(JSON.stringify(data1));
            // 请求完接口1后,继续请求接口2
            return queryData('http://localhost:3000/api2');
        },
        error1 => {
            console.log(error1);
        }
    )
    .then(
        data2 => {
            console.log(JSON.stringify(data2));
            // 请求完接口2后,继续请求接口3
            return queryData('http://localhost:3000/api3');
        },
        error2 => {
            console.log(error2);
        }
    )
    .then(
        data3 => {
            // 获取接口3返回的数据
            console.log(JSON.stringify(data3));
        },
        error3 => {
            console.log(error3);
        }
    );

3、函数返回Promise对象

返回值为一个 Promise 对象的函数称作 Promise 函数,它常常用于开发基于异步操作的库。

例如,

/*
  基于Promise发送Ajax请求
*/
function queryData(url) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState != 4) return;
            if (xhr.readyState == 4 && xhr.status == 200) {
                // 处理正常情况
                resolve(xhr.responseText);
            } else {
                // 处理异常情况
                reject('接口请求失败');
            }
        };
        xhr.responseType = 'json'; // 设置返回的数据类型
        xhr.open('get', url);
        xhr.send(null); // 请求接口
    });
}
// 发送多个ajax请求并且保证顺序
queryData('http://localhost:3000/api1')
    .then(
        data1 => {
            console.log(JSON.stringify(data1));
            return queryData('http://localhost:3000/api2');
        },
        error1 => {
            console.log(error1);
        }
    )
    .then(
        data2 => {
            console.log(JSON.stringify(data2));
            // 注意:返回的是 Promise 实例对象,
            // 如果返回的是具体值时,则会产生一个新的默认的 promise实例,来调用这里的then,
            // 确保可以继续进行链式操作获取结果。相当于return 'cjavapy';
            return new Promise((resolve, reject) => {
                resolve('cjavapy');
            });
        },
        error2 => {
            console.log(error2);
        }
    )
    .then(data3 => {
        console.log(data3);
    });

4、Promise方法(Promise.all()和Promise.race())

Promise.all():并发处理多个异步任务,所有任务都执行成功,才能得到结果。

Promise.race(): 并发处理多个异步任务,只要有一个任务执行成功,就能得到结果。

Promise.all()示例:

var p1 = Promise.resolve(1),
p2 = Promise.resolve(2),
p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
console.log(results); // [1, 2, 3]
});

或者

/*
  封装 Promise 接口调用
*/
function queryData(url) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState != 4) return;
            if (xhr.readyState == 4 && xhr.status == 200) {
                // 处理正常结果
                resolve(xhr.responseText);
            } else {
                // 处理异常结果
                reject('服务器错误');
            }
        };
        xhr.open('get', url);
        xhr.send(null);
    });
}
var promise1 = queryData('http://localhost:3000/p1');
var promise2 = queryData('http://localhost:3000/p2');
var promise3 = queryData('http://localhost:3000/p3');
Promise.all([promise1, promise2, promise3]).then(result => {
    console.log(result);
});

Promise.race()示例:

var p1 = Promise.resolve(1),
p2 = Promise.resolve("error1"),
p3 = Promise.reject("error2");
Promise.race([p1, p2, p3]).then(function (results) {
console.log(results); //1
});

或者

/*
  封装 Promise 接口调用
*/
function queryData(url) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState != 4) return;
            if (xhr.readyState == 4 && xhr.status == 200) {
                // 处理正常结果
                resolve(xhr.responseText);
            } else {
                // 处理异常结果
                reject('服务器错误');
            }
        };
        xhr.open('get', url);
        xhr.send(null);
    });
}
var promise1 = queryData('http://localhost:3000/p1');
var promise2 = queryData('http://localhost:3000/p2');
var promise3 = queryData('http://localhost:3000/p3');
Promise.race([promise1, promise2, promise3]).then(result => {
    console.log(result);
});

推荐阅读
cjavapy编程之路首页