定义
在JavaScript是世界里,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数传统的异步编程最大的特点就是地狱般的回调嵌套,一旦嵌套次数过多,就很容易使我们的代码难以理解和维护。
而Promise则可以让我们通过链式调用的方法去解决回调嵌套的问题,使我们的代码更容易理解和维护,而且Promise还增加了许多有用的特性,让我们处理异步编程得心应手。
Promise是一个构造函数。从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态);状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
剖析Promise
创建一个Promise
// 我们先使用ES5的语法
var promise = new Promise(function(resolve, reject) {
var flag = Math.random();
setTimeout(function() {
if(flag) {
resolve('success');
}
else {
reject('fail');
}
}, 1000);
});
console.log(promise); // 在浏览器的控制台运行的话,它返回的是一个包含了许多属性的Promise对象;在Node.js环境中控制台输出 Promise { <pending> }。
promise.then(function(result) {
console.log(result);
}, function(err) {
console.log(err);
}); // 1s后这里的输出可能是fail也可能是success
- 因为Promise是一个构造函数,所以我们使用了new操作符来创建promise。
- 构造函数Promise的参数是一个函数(暂时叫它func),这个函数(func)有两个参数resolve和reject,它们分别是两个函数,这两个函数的作用就是将promise的状态从pending(等待)转换为resolved(已解决)或者从pending(等待)转换为rejected(已失败)。
- 创建后的promise有一些方法,then和catch。当然我们也可以人为的在Promise函数上添加一些满足我们自己需求的方法,方便每一个promise对象使用。
//ES6写法
let p = new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve('success') : reject('fail');
}, 1000)
});
console.log(p);
p.then((result) => {
console.log(result);
}, (err) => {
console.log(err);
});
其实可以这样理解,Promise函数体的内部包裹着一个异步的请求或者操作或者函数;然后我们可以在这个异步的操作完成的时候使用resolve函数将我们获得的结果传递出去,或者使用reject函数将错误的消息传递出去。
then
Promise对象可以通过使用then方法将上一步返回的结果获取过来(不管是resolved还是rejected)。
let p = new Promise((resolve, reject) => {
let flag = Math.random() > 0.5 ? true : false;
if(flag) {
console.log('使用resolve将promise状态从pending变为resolved');
resolve('success');
}
else {
console.log('使用reject将promise状态从pending变为rejected');
reject('fail');
}
});
// @1
p.then((result) => {
console.log('接受resolved的结果');
console.log(result);
}, (err) => {
console.log('捕获错误的结果');
console.log(err);
});
我们可以看到,then方法可以接受两个函数作为参数,第一个函数是用来处理resolve的结果,第二个是可选的,用来处理reject的结果。也就是说,我们在创建p这个Promise对象的时候,通过函数resolve传递出去的结果可以被p的第一个then方法中的第一个函数捕获然后作为它的参数。通过函数reject传递出去的结果可以被p的第一个then方法中的第二个函数捕获然后作为它的参数。
then链式操作
let p1 = new Promise((resolve, reject) => {
let flag = Math.random() > 0.5 ? true : false;
resolve();
});
// @2 使用then方法进行链式的调用
p1.then(() => {
return 1;
}).then((result) => {
console.log(result);
return 'hello'
}).then((result) => {
console.log(result);
});
// @3 在then方法内部可以再次使用异步的操作
p1.then(() => {
console.log('******');
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
}, 1000);
});
return p1;
}).then((result) => {
console.log(result);
});
//打印结果
******
1
hello
123
从上面的代码中我们可以看到,进行链式的调用,可以把每一次的结果都返还给下一个then方法,然后在下一个then方法中对这个值进行处理。每一个then方法中都可以再次新创建一个Promise对象,然后返还给下一个then方法处理。
catch
这个方法其实是then方法的一种特例。
.then(null, rejection)
相当于我们不使用then方法的第一个函数,只是用第二个函数。原本then的第二个参数是用来处理promise执行错误的结果的。catch函数比较简单,就是用来捕获之前的then方法里面的异常。
all
谁跑的慢,以谁为准执行回调。
Promise.all方法用来包装许多个Promise实例,然后组成了一个新的Promise对象,新的Promise对象的状态由前面几个被包裹的Promise对象的状态决定,如果前面的Promise都被resolve了,那么新的Promise的状态也是resolve的;只要有一个Promise被reject了,那么组成的新的Promise的状态也是reject的。
有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,是不是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。
race
谁跑的快,以谁为准执行回调
race的使用场景:比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作。
假如有一个请求任务,我希望5秒内如果还没请求到就返回请求超时。这时就可以用race了。5秒后请求还没完成,执行请求超时的函数。
Promise.race方法和上面的Promise.all有点类似,都是包装许多的Promise对象,然后组成了一个新的Promise对象,但是使用Promise.race的含义是:只要包裹的的Promise对象中有一个的状态发生了改变,那么组成的这个新的Promise对象的状态就是上面那个率先改变的Promise实例的状态。
//请求某个图片资源
function requestImg(){
var p = new Promise((resolve, reject) => {
var img = new Image();
img.onload = function(){
resolve(img);
}
img.src = '图片的路径';
});
return p;
}
//延时函数,用于给请求计时
function timeout(){
var p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('图片请求超时');
}, 5000);
});
return p;
}
Promise.race([requestImg(), timeout()]).then((data) =>{
console.log(data);
}).catch((err) => {
console.log(err);
});
requestImg函数会异步请求一张图片,我把地址写为"图片的路径",所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。
resolve
Promise.resolve方法主要是将一个值转变为一个Promise对象,然后使它具有Promise的一些方法和特性,为了满足我们一些特殊情况下的要求。
let arr = [null, 0, 'hello',
{ then: function() { console.log(' a thenable obj')}}
];
arr.map((value) => {
return Promise.resolve(value);
});
console.log(arr);
上面的输出结果如下:
[ null, 0, 'hello', { then: [Function: then] } ]
a thenable obj // Promise.resolve方法会将具有then方法的对象转换为一个Promise对象,然后就立即执行then方法。
reject
Promise.reject方法和Promise.resolve方法一样,只不过通过Promise.reject方法产生的Promise对象的状态是rejected的,下面是一个示例:
let p = Promise.reject('fail');
p.catch((err) => {
console.log(err);
}); // fail
总结:
-
promise是一个构造函数。里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。新的异步的解决方式,将异步代码转换为同步代码的书写方式。 -
promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态)。状态一旦改变,就不会再变。创造promise实例后,它会立即执行。 -
Promise构造函数的参数:resolve , reject ;方法:all ,race,resolve,reject,被Promise类调用,返回值也是一个Promise对象。 -
Promise原型里的方法:then(),catch (), ,finally -
Promise的参数是一个函数,这个函数又有两个参数,resolve,reject。其实这两个参数也是两个函数。- resolve :异步操作执行成功后的回调函数
- reject:异步操作执行失败后的回调函数
- then()方法。将上一步返回的结果获取过来,处理。
- catch() 方法。其实是then方法的一种特例。相当于我们不使用then方法的第一个函数,只是用第二个函数。用来捕获之前的then方法里面的异常。
- all()方法。包装许多个Promise实例,然后组成了一个新的Promise对象。谁跑的慢,以谁为准执行回调。
- race()方法。谁跑的快,以谁为准执行回调。那个最先执行就返回那个。
- resolve() 方法。将一个值转变为一个Promise对象。但状态是成功的。
- reject() 方法。将一个值转变为一个Promise对象。但状态是失败的。