所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。(这里的翻译源自ECMAScript 2015关于Promise的解释,没有原文翻译MDN的原话,如果您有疑问,可以参看英文的说明文档:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise---译者注)
new Promise(executor); new Promise(function(resolve, reject) { ... });
resolve
、
reject
两个参数
的一个
函数。这个函数在创建Promise对象的时候会立即得到执行(在Promise构造函数返回Promise对象之前就会被执行),并把成功回调函数(resolve)和失败回调函数(reject)作为参数传递进来。调用成功回调函数(resolve)和失败回调函数(reject)会分别触发promise的成功或失败。这个函数通常被用来执行一些异步操作,操作完成以后可以选择调用成功回调函数(resolve)来触发promise的成功状态,或者,在出现错误的时候调用失败回调函数(reject)来触发promise的失败。
Promise
对象是一个返回值的代理,这个返回值在promise对象创建时未必已知。它允许你为异步操作的成功返回值或失败信息指定处理方法。 这使得异步方法可以像同步方法那样返回值:异步方法会返回一个包含了原返回值的 promise 对象来替代原返回值。
Promise
对象有以下几种状态:
pending状态的promise对象既可转换为带着一个成功值的fulfilled 状态,也可变为带着一个失败信息的 rejected 状态。当状态发生转换时,promise.then绑定的方法(函数句柄)就会被调用。(当绑定方法时,如果 promise对象已经处于 fulfilled 或 rejected 状态,那么相应的方法将会被立刻调用, 所以在异步操作的完成情况和它的绑定方法之间不存在竞争条件。)
因为Promise.prototype.then
和
方法返回 promises对象, 所以它们可以被链式调用—— 一种被称为 composition 的操作。Promise.prototype.catch
Promise.length
Promise.prototype
Promise
构造器的原型.
Promise.all(iterable)
Promise.race(iterable)
Promise.reject(reason)
Promise.resolve(value)
Promise对象。如果该value为可继续的(thenable,即带有then方法),返回的Promise对象会“跟随”这个value,采用这个value的最终状态;否则的话返回值会用这个value满足(fullfil)返回的Promise对象。
Promise
原型Promise.prototype.constructor
Promise
函数.
Promise.prototype.catch(onRejected)
Promise.prototype.then(onFulfilled, onRejected)
我们先看一个最简单的Promise例子:生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:
function test(resolve, reject) {
var timeOut = Math.random() * 2;
log('set timeout to: ' + timeOut + ' seconds.');
setTimeout(function () {
if (timeOut < 1) {
log('call resolve()...');
resolve('200 OK');
}
else {
log('call reject()...');
reject('timeout in ' + timeOut + ' seconds.');
}
}, timeOut * 1000);
}
这个test()
函数有两个参数,这两个参数都是函数,如果执行成功,我们将调用resolve('200 OK')
,如果执行失败,我们将调用reject('timeout in ' + timeOut + ' seconds.')
。可以看出,test()
函数只关心自身的逻辑,并不关心具体的resolve
和reject
将如何处理结果。
有了执行函数,我们就可以用一个Promise对象来执行它,并在将来某个时刻获得成功或失败的结果:
var p1 = new Promise(test);
var p2 = p1.then(function (result) {
console.log('成功:' + result);
});
var p3 = p2.catch(function (reason) {
console.log('失败:' + reason);
});
变量p1
是一个Promise对象,它负责执行test
函数。由于test
函数在内部是异步执行的,当test
函数执行成功时,我们告诉Promise对象:
// 如果成功,执行这个函数:
p1.then(function (result) {
console.log('成功:' + result);
});
当test
函数执行失败时,我们告诉Promise对象:
p2.catch(function (reason) {
console.log('失败:' + reason);
});
Promise对象可以串联起来,所以上述代码可以简化为:
new Promise(test).then(function (result) {
console.log('成功:' + result);
}).catch(function (reason) {
console.log('失败:' + reason);
});
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
介绍了Promise,那么来说一下它的两个特点吧。
对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成)和 Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。
说了这么多,相信笔者也迫不及待了,先来构建一个Promise看看吧。
functiongetPromise1(){
var p = new Promise((resolve, reject) => {
// 这里放一些异步操作
setTimeout(()=> {
console.log('异步1执行完成');
resolve('异步1返回数据');
}, 1000);
});
return p;
}
getPromise1();
看到这里读者肯定觉得跟以前比没有很大的变化,而且有很多的困惑,比如:
运行上面的代码,控制台就会打印”异步1执行完成”。我们只是构造了一个Promise的对象,并没有调用它里面的方法,就已经执行了,所以这就是为什么要将Promise放到函数中调用获取的原因。同时,使用一个函数返回对象更加符合函数式编程的思想。这边的resolve方法的作用就是将Promise对象从Pending状态置为Resolved状态。
getPromise1()
.then((data) => {
// 一些业务逻辑处理
console.log(data);
});
getPromise1方法获取到的就是我们上面返回的Promise对象,直接调用then方法,表示在异步结束后调用此方法。它接受一个参数,是一个函数,这个函数默认会传入一个参数,这个参数就是我们在Promise构造函数中所调用的resolve所传入的异步数据。
感情绕了一大圈,其实就是把原有在异步完成后的业务逻辑单独抽离出一个方法么?其实Promise还能做更多。
谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里
如果这个时候来了一个需求,这个异步的数据不够,还需要发另外一个异步。如果按照以前的逻辑肯定是在then回调方法的继续来发异步,然后就陷入了恶(e)性(xin)的嵌套,如果业务逻辑很复杂,而且还需要发异步,那么这个函数里面代码也会越来越庞大,后期维护起来会非常的麻烦。但是Promise的出现拯救了这一切。
Promise的优势在于能够进行链式的调用,将原来嵌套调用转为线性调用。在then方法中继续返回一个新的Promise对象,然后能够继续调用then方法。
getPromise1()
.then((data) => {
console.log(data);
return getPromise2();
})
.then((data) => {
console.log(data);
return getPromise3();
})
.then((data) => {
console.log(data);
});
functiongetPromise2(){
var p = new Promise((resolve, reject) => {
// 这里放一些异步操作
setTimeout(()=> {
console.log('异步2执行完成');
resolve('异步2返回数据');
}, 2000);
});
return p;
}
functiongetPromise3(){
var p = new Promise((resolve, reject) => {
// 这里放一些异步操作
setTimeout(()=> {
console.log('异步3执行完成');
resolve('异步3返回数据');
}, 3000);
});
return p;
}
我们会看到每隔一秒、两秒、三秒就会打印一组“执行完成n和返回数据n”。在then方法中也可以不返回一个Promise对象,直接返回数据。将上面的代码如下改写:
getPromise1()
.then((data) => {
console.log(data);
return data;
})
.then((data) => {
console.log(data);
return data;
})
.then((data) => {
console.log(data);
});
最后可以看到,一秒之后打印了一次执行完成1和三次返回数据1。这样的then方法没有什么意义。
细心的读者可能发现了,在Promise的构造方法中还有一个reject方法还没有被用到。既然是异步,那么肯定有成功也有失败的时候,reject方法的作用是将Promise对象的状态置为Rejected状态,在then方法中执行失败情况的回调函数。
functiongetPromise4(){
var p = new Promise((resolve, reject) => {
setTimeout(()=> {
var number = Math.round(Math.random()*10);
if(number %2 == 0) {
resolve('成功数据');
} else {
reject('失败数据')
}
}, 100);
});
return p;
}
getPromise4()
.then(
(data) => {
console.log('成功回调:' + data);
},
(data) => {
console.log('失败回调:' + data);
}
)
我们首先获取一个随机数,判断这个随机数是否是偶数,如果是偶数的话就进入成功的回调方法;如果失败了就进入失败的回调方法。在then方法中我们发现多传入了一个方法,第一个方法还是成功情况的回调,第二个方法就是失败情况的回调,可以不传,不传的话就默认只有成功的回调。
但是需要 注意 的是,如果不传失败的回调函数,但是同时你还调用了reject方法,这时候Promise内部会报错。
在Promise对象上还有一些其他方法。
在Promise的原型上还有一个catch方法,我们知道try/catch方法是用来捕捉异常的,Promise中的catch方法也可以做同样的事情。将上面的方法进行如下改写:
getPromise4()
.then(
(data) => {
console.log('成功回调:' + data);
console.log(temp);
},
(data) => {
console.log('失败回调:' + data);
console.log(temp);
}
)
.catch((err)=>{
console.log('捕获异常',err);
})
在执行Promise的回调方法时可能会进入到第一个成功的回调函数中也可能会进入到第二个失败的回调函数中,如果回调函数中有抛出异常,并不会因为这个异常而卡死程序,就会进入到catch方法中捕捉到异常。最终运行的效果有以下两种可能性:
Promise中还有一个all方法,all是全部的意思,因此我们能猜测,就是等所有的异步都执行完毕。all方法让Promise有并行执行异步的能力,等所有并行异步执行完成后才执行回调。
Promise.all([getPromise1(),getPromise2(),getPromise3()])
.then((results) => {
console.log('所有异步结束', results)
});
可以看到all接受一个Promise的数组,数组中是三个Promise对象。在第一个和第二个异步执行完成后都没有进入then方法,而是等最后一个最慢的异步执行完了才进入then方法。最终,所有异步操作的结果都通过then方法的参数以数组的形式传递进来。最终运行效果如下:
但是问题来了,如果多个异步中有一个异步执行失败了呢?如果这个异步失败是通过reject方法抛出的,那么此时其他Pending中的异步还是会继续去运行,但是这个时候就会提前进入then方法的第二个参数函数中去,这个函数的默认参数也只是这个失败异步reject所发送的数据(不一定还是数组),等其他异步执行完成也不会再去执行then方法了。
all方法执行的效果是 等大家都结束了再运行
,但是race是赛跑、竞争的意思,那么就很明显了,就是 谁跑的快就有肉吃
。将上面的代码进行改写:
Promise.race([getPromise1(),getPromise2(),getPromise3()])
.then((results) => {
console.log('results', results)
});
这三个异步同样是并行执行的,但是race的then方法优先执行先完成的异步。第一个异步getPromise1先执行完,因此先进入then方法。此时getPromise2和getPromise3还没有执行完,还会继续执行,但是不会再去执行then方法了。最后执行结果如下: