Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。 Promise 对象有以下两个特点:
1、对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:
pending: 初始状态,不是成功或失败状态。
fulfilled: 意味着操作成功完成。
rejected: 意味着操作失败。
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
怎么理解上述这段概念呢?
简单来说就是promise是处理异步操作的,它有三种状态,初始状态(pending)肯定是必须的,初始状态(pending)也只能变为成功状态(fulfilled)或者失败状态(rejected)的一种,而且状态的改变是不可逆的。
概念性的话不好理解,还是手动操作一下比较好理解。
首先创建一个promise对象:
Promise 构造函数包含一个参数和一个带有 resolve(解析)和 reject(拒绝)两个参数的回调。在回调中执行一些操作(例如异步),如果一切都正常,则调用 resolve,否则调用 reject。
var p = new Promise(function (resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
});
只需要知道对象里面是处理异步的、Promise 构造函数的两个参数一个表示成功时候的回调一个表示失败时候的回调就可以了。
举个栗子:
//注:本例中当time为0 的时候就表示调用失败
var p = new Promise(function (resolve, reject) {
setTimeout(() => {
if (time == 0) {//当拒绝(失败)时候调用reject
reject('失败了')
} else {//当解析(成功)的时候调用resolve
resolve('调用' + time + '秒后执行的')
}
}, time * 1000);
})
对于已经实例化过的 promise 对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。
//打印结果:调用2秒后执行的
function runAsync(time) {
var p = new Promise(function (resolve, reject) {
setTimeout(() => {
if (time == 0) {
reject('失败了')
} else {
resolve('调用' + time + '秒后执行的')
}
}, time * 1000);
})
return p
}
runAsync(2).then(res => {
console.log(res)
}).catch(err=>{
console.log(err)
})
//本例会在2秒后打印"调用2秒后执行的",因为在promise里面走的是resolve(成功的回调),如果将参数改为0的话就会走reject失败的回调)
看到这里是不是觉得也就那么回事,跟普通函数没什么区别,但是要知道,异步代码是需要等待的,当有多个异步代码要执行时,调用顺序就极为重要。
举个栗子:
我要得到的顺序是:洗漱-上床-睡觉
/*
睡觉
上床
洗漱
*/
打印结果:
function runAsync(time,behavior) {
setTimeout(() => {
console.log(behavior)
}, time * 1000);
}
runAsync(3,'洗漱')
runAsync(2,'上床')
runAsync(1,'睡觉')
//本例是1秒后打印睡觉,2秒后打印上床,3秒后打印洗漱,因为是同步的方法调用
顺序不对,不过可以通过回调的方式来解决:
/*
洗漱
上床
睡觉
*/
function runAsync(time,behavior,callback) {
setTimeout(() => {
console.log(behavior)
callback(behavior)
}, time * 1000);
}
runAsync(3,'洗漱',function(data) {
//console.log(data)
runAsync(2,'上床',function (data) {
//console.log(data)
runAsync(1,'睡觉',function (data) {
//console.log(data)
})
})
})
//本例是在3秒后打印洗漱,打印完洗漱2后打印上传,打印完上床1秒后打印睡觉
上面的例子是符合要求的,没问题,但是睡觉前还要敷面膜、看电影、撸猫等等操作依次执行的话,就会形成回调地狱,变得累赘且维护起来很差的问题,而且,没有一点可读性!像这样:
runAsync(3, '洗漱', function (data) {
//console.log(data)
runAsync(2, '上床', function (data) {
//console.log(data)
runAsync(1, '敷面膜', function (data) {
//console.log(data)
runAsync(1, '看电影', function (data) {
//console.log(data)
runAsync(1, '撸猫', function (data) {
//console.log(data)
runAsync(1, '睡觉', function (data) {
//console.log(data)
})
})
})
})
})
})
但是,如果用promise的话,就能避免回调地狱的问题,而且维护起来也比较方便,上面的代码就可以改造一下:
/*
洗漱
上床
睡觉
*/
function runAsync(time,behavior) {
var p = new Promise(function (resolve, reject) {
setTimeout(() => {
if (time == 0) {
reject('失败了')
} else {
resolve(behavior)
}
}, time * 1000);
})
return p
}
runAsync(3,'洗漱').then(res => {
console.log(res)
return runAsync(2,'上床')
}).then(res => {
console.log(res)
return runAsync(1,'睡觉')
}).then(res => {
console.log(res)
}).catch(err=>{
console.log(err)
})
//本例是在3秒后打印洗漱,打印完洗漱2后打印上传,打印完上床1秒后打印睡觉
结果是一样的,是不是看起来就清晰了很多呢?
从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
简单来说,promise.all就是在所有作为参数的promise对象按顺序执行
完之后再返回所有的值,比如开发中有个组件需要多个请求完成之后再进行渲染,这时候用promise.all就非常nice了
这里要划重点:是按顺序执行的
//["洗漱", "上床", "睡觉"]
function runAsync(time,behavior) {
var p = new Promise(function (resolve, reject) {
setTimeout(() => {
if (time == 0) {
reject('失败了')
} else {
resolve(behavior)
}
}, time * 1000);
})
return p
}
Promise.all([runAsync(3,'洗漱'), runAsync(2,'上床'), runAsync(1,'睡觉')]).then(res => {
console.log(res)
}).catch(err=>{
console.log(err)
})
//本例是在3秒后打印["洗漱", "上床", "睡觉"],因为promise.all是等所有异步结果执行完之后再返回结果
需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即runAsync(3,‘洗漱’)的结果在前,即便runAsync(3,‘洗漱’)的结果获取的比runAsync(1,‘睡觉’)要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。
顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([runAsync(3,‘洗漱’),runAsync(2,‘上床’), runAsync(1,‘睡觉’)])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
简单来说就是所有异步操作不管成功还是失败,哪个先执行完就返回那个的值,但是好像没用过这个方法。
//睡觉
function runAsync(time,behavior) {
var p = new Promise(function (resolve, reject) {
setTimeout(() => {
if (time == 0) {
reject('失败了')
} else {
resolve(behavior)
}
}, time * 1000);
})
return p
}
Promise.race([runAsync(3,'洗漱'), runAsync(2,'上床'), runAsync(1,'睡觉')]).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
//本例是在1秒后打印'睡觉',因为promise.race返回最先执行完的结果
不管是promise对象还是promise.all,只要是遇到失败,peomise的状态都会变为rejected,不会执行后面.then(),而是直接走.catch()方法。
promise对象在遇到失败的时候
/*
洗漱
失败了
*/
function runAsync(time,behavior) {
var p = new Promise(function (resolve, reject) {
setTimeout(() => {
if (time == 0) {
reject('失败了')
} else {
resolve(behavior)
}
}, time * 1000);
})
return p
}
runAsync(3,'洗漱').then(res => {
console.log(res)
return runAsync(0,'上床')//时间为0的时候会走失败
}).then(res => {
console.log(res)
return runAsync(1,'睡觉')
}).then(res => {
console.log(res)
}).catch(err=>{
console.log(err)
})
此例是在3秒后打印"洗漱",然后在catch中捕获失败,打印"失败了",而没有走后面的异步操作,是因为遇到错误的时候,会直接走catch(),错误后面的then()不会再执行
promise.all遇到失败的时候
//失败了
function runAsync(time, behavior) {
var p = new Promise(function (resolve, reject) {
setTimeout(() => {
if (time == 0) {
reject('失败了')
} else {
resolve(behavior)
}
}, time * 1000);
})
return p
}
Promise.all([runAsync(3, '洗漱'), runAsync(0, '上床'), runAsync(1, '睡觉')]).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
//本例中是没有等待直接打印"失败了",是因为promise.all在执行所有异步的时候是一起开始执行的,0秒开始执行的操作失败了,promise的状态已经变为了失败,所以其他的操作也就不会再执行了
promise.race在遇到失败的时候跟promise.all遇到失败的时候是一样的,不同的是promise.race不管成功失败,只返回执行结果最快的那个