promise对象是javaScript的异步操作解决方案,,为异步操作提供统一的接口。它起到代理的作用(proxy),充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口。Promise可以让异步操作写起来,就像在写同步操作的流程,而不必一层层的嵌套回调函数。
Promise的设计思想是,所有异步任务都返回一个Promise实例,Promise实例有一个then方法,用来指定下一步的回调函数。
var asyncFn = function(){
return new Promise(function(resolve,reject){
//异步操作··· ···
})
}
asyncFn.then(function(){})
Promise对象的状态:
promise对象通过自身的状态,来控制异步操作。Promise实例有三种状态:
· 异步操作未完成(pending)
· 异步操作成功(resolved)
· 异步操作失败(rejected)
这三种的状态的变化途径只有两种:
· 从“未完成”到“成功”
· 从“未完成”到“失败”
一旦状态发生变化,就凝固了,不会再有新的状态变化。这就是Promise名字的由来,它的英文意思是“承诺”,一旦承诺成效,就不得改变了。
因此,Promise的最终结果只有两种。
· 异步操作成功,Promise实例传回一个值(value),状态变为resolved。
· 一步操作失败,Promise实例抛出错误(error),状态变为rejected。
Promise构造函数:
Promsie构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,他们是两个函数,由js引擎提供,不用自己实现。
resolve函数的作用是 —— 将Promise实例的状态从“未完成”变为”成功”(既从pending变为fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
reject函数的作用是—— 将Promise实例的状态从“未完成”变为“失败”(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
eg:
var asyncFn = function(){
return new Promise(function(resolve,reject){
setTimeout(function(){
resolve("成功")
},2000)
})
}
asyncFn().then(function(getParam){
console.log(getParam);//成功
})
上边代码:2秒后打印“成功”,,,Promise实例的状态从 “未完成” 变为“成功”,(既从pending变为fulfilled),执行resolve函数,并把参数传递给回调函数。
Promise.prototype.then():
then方法可以接受两个回调函数,第一个是异步操作成功时(变为resolved状态)的回调函数,第二个是异步操作失败(变为rejected)时的回调函数(该参数可以省略)。一旦状态改变,就会调用相应的回调函数。
eg:来看下一个图片加载的小例子
var imgOnload =function(path){
return new Promise(function(resolve,reject){
var myImg = new Image();
myImg.onload = resolve;
myImg.onerror = reject;
myImg.src = path;
})
};
imgOnload("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561950592783&di=2cf2061e2eab1de893a8cf80b0f6181b&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20171117%2F9ad732c1a18c4bd487943c8b3884a7b4.gif")
.then(function(e){
console.log(e)//Event {isTrusted: true, type: "load", target: img, currentTarget: img, eventPhase: 2, …}
$("body").append(e.target)
},function(error){
console.log(error);
})
上边代码:在Promise参数函数中,我们创建了一个图片对象实例,它有两个事件监听属性,onload:图片加载成功后调用;
onerror:属性在加载失败调用。当图片加载成功后,onload属性会返回一个事件对象,所以我们通过then方法的回调函数,会接收到这个事件对象,该对象的target属性就是图片加载成功后生成的Dom节点。。。
Promise.prototype.catch():
用于指定错误发生时的回调函数;
new Promise(function(resolve,reject){
throw new Error("语法错误");
}).catch(function(error){
console.log(error)//Error:语法错误
})
上边代码,promise跑出一个错误,被catch方法指定的回调函数捕获。上边我们提到的reject方法跟它是一样的,也是抛出一个错误。
如果Promsie状态已经变成resolved,在抛出错误是无效的:
new Promise(function(resolve,reject){
resolve("成功");
throw new Error("语法错误");
}).then(function(param){
console.log(param);//成功
}).catch(function(error){
console.log(error)//无返回
})
上边代码,在Promise参数函数中,resolve后边抛出一个错误,下边catch未捕获到,。因为Promise的状态一旦改变,就永久保持该状态,不会变了。
练习题:
1、下边输出结果为?输出promise2的状态刚开始是?
const promise = new Promise((resolve, reject) => {
throw 1
})
const promise2 = promise.then((res) => {
console.log('res:', res)
return res
}).catch(err => {
console.log('err', err)
return err
})
console.log('status:', promise2)
解答:
第一问:结果很容易,抛出是1,所以走的是catch,打印结果:'err:' 1
第二问:输出的状态刚开始一定为pedding,这个一点毛病都没有,当catch里边的异步函数执行完成时,此时的状态决定promise2的状态,如果抛出异常,promise2的状态为rejected;如果没有异常,promise2的状态为resolved。
2、解释下边2段代码,为什么输出的promise2的状态结果不同?
const promise = new Promise((resolve, reject) => {
throw 1
})
const promise2 = promise.then((res) => {
return res
}).catch(err => {
throw err
})
console.log('status:', promise2) // 'rejected'
const promise = new Promise((resolve, reject) => {
throw 1
})
const promise2 = promise.then((res) => {
return res
}).catch(err => {
setTimeout(() => {
console.log('err', err)
throw err
}, 2000)
})
console.log('status:', promise2) // 'resolved'
解答:
整明白上边的代码,必须明白:(1)如果没有异常,promise返回到状态都是resolved;(2)状态一旦凝固(状态已定),后边将不可能修改其状态。
所以上边最后一段代码,catch中虽然有一个异常抛出,但是毕竟setTimeout是一个异步函数,所以catch默认返回的是resolved状态,2秒钟之后,即使抛出异常也不能够修改其状态了,因为状态早已经凝固(定)了。
Promise.prototype.finally():
finally方法用于指定不管Promise对象最后的状态如何,都会执行的操作。该方法是ES2018引入的标准。
var promise = new Promise(function(resolve,reject){
resolve("成功");
})
promise.then(function(param){
console.log(param)//成功
}).finally(function(){
console.log("执行完毕")//执行完毕
})
补充:
1、resolve( ) / reject(): 直接返回resolve( ) 或 reject()
相当于
Promise.resolve( ) / Promise.reject( )
在实际开发中的例子(uni-app为例):
下边其实是封装了一个发送数据的方法,里边有好多逻辑,只不过是简化了,判断后台返回数据中的code码。
const request = (param) => {
return new Promise((resolve, reject) => {
// 请求数据
let requestTask = uni.request({
url: 'www.xxx.com',
method: 'GET',
data: { ··· }
success: (res) => {
if (res.data.result.status === -1) {
reject();
} else if (res.data.result.status === 0) {
reject();
}
resolve(res.data.result); // 返回后台传过来的数据
},
fail (e) {
reject(e)
}
}
})
}
2、promise.all():参数是一个装有一个或多个promise对象的数组
(1)下边例子中,all后边的打印输出,要等等到所有promise都执行完之后才能输出
const promiseAll = []
for(let i=0; i<10; i++) {
promiseAll.push(new Promise((resolve, reject) => {
setTimeout(() => {
resolve(i)
}, 1000)
}))
}
Promise.all(promiseAll).then((res) => {
console.log(res) //[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
})
(2)实际开发中的一个例子(小程序云开发):
下边这个是在小程序云开发中的一个真实例子,小程规定:每次从云数据库中获取的数据是有限制的,100条,需求是必须要从云数据库中获取到全部数据,与后台返回的数据去比对,如果后台返回到数据与云数据库的数据不同才会存到云数据库中去:
async function getCloudDatabaseAllData(dbData) {
// 参数dbData就是await db.collection('playlist').count() 获取云数据库中数据的数量方法
const MAX_COUNT = 100 // 云数据库每次取数据最多只能取100条
const _totals = dbData.total // 获取云数据库中数据总的数量
const times = Math.ceil(_totals / MAX_COUNT) // 获取循环次数
const tasks = [] // 作为Promise.all的参数,每次请求的数据 [{}, {}, ··· ···]
for(let i = 0; i < times; i ++) {
const getdbDataPromise = db.collection('playlist').skip(i * MAX_COUNT).limit(MAX_COUNT).get() // 从第几位开始取数据,一次取多少个数据
tasks.push(getdbDataPromise) // 把取到的数据push到tasks中去
}
const res = await Promise.all(tasks) // [{}, {}, {}]
return res.reduce((prev, next) => {
return {
data: prev.data.concat(next.data) // 这里取到的数据是一个对象,对象中的data才是我们真正想要的数据
}
})
}