Promise是什么?
Promise 是异步编程的一种解决方案,Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
—— 阮一峰《ECMAScript 6 入门》
Promise主要解决什么问题?
由于javascript是一门单线程的语言,所以我们早期来处理异步场景的时候,大部分是通过回调函数来进行处理的。
var fn = function(callback){
setTimeout(function(){
callback()
},1000)
}
fn(function(){console.log('hello,萘胺')})
例如上面这个例子,fn
函数是一个异步函数,里面执行的setTimeout
将会在1s之后调用传入的callback
函数,打印出hello,萘胺
这个结果。
但是当我们有多个异步操作的时候,就需要有多个异步函数进行嵌套,代码将会变得更加臃肿和难以维护。
setTimeout(function(){
console.log('执行了')
setTimeout(function(){
console.log('再次执行了')
//.....
},2000)
},1000)
同样的,还有一个例子:
假设我们有fn1
,fn2
,fn3
三个异步函数,
var fn1 = function(){
setTimeout(function(){
console.log('hellow,小明')
},1000)
}
var fn2 = function(){
setTimeout(function(){
console.log('hellow,小红')
},3000)
}
var fn3 = function(){
setTimeout(function(){
console.log('hellow,小花')
},2000)
}
我们想顺序对三个函数的结果进行顺序打印,那么使用传统的回调函数来实现的话,我们可以这样写:
var fn1 = function(callback){
setTimeout(function(){
console.log('hellow,小明')
callback()
},1000)
}
var fn2 = function(callback){
setTimeout(function(){
console.log('hellow,小红')
callback()
},3000)
}
var fn3 = function(callback){
setTimeout(function(){
console.log('hellow,小花')
callback()
},2000)
}
fn1(function(){
fn2(function(){
fn3(function(){
console.log('结束了~')
})
})
})
//或者
var makefn = function(text,callback,timer){
setTimeout(function(){
console.log(text)
callback()
},timer)
}
makefn('hellow,小明',function(){
makefn('hellow,小红',function(){
makefn('hellow,小花',function(){
console.log('结束了~')
},2000)
},3000)
},1000)
根据上面的例子我们可以看出异步函数之间层层嵌套,形成了回调地狱
。
回调地狱
就是指把函数作为参数层层嵌套请求,我们将其称之为回调地狱,代码阅读性非常差。
Promise基本用法
Promise对象
是一个构造函数,用来生成Promise实例
。
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。
resolve函数
:
将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject函数
:
将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
const person = new Promise((resolve,reject) => {
let num = 6;
if(num>5){
resolve()
}else{
reject()
}
})
Promise实例生成以后,可以用then
方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved
时调用,第二个回调函数是Promise对象的状态变为rejected
时调用。其中,第二个函数是可选的,这两个函数都接受Promise对象传出的值作为参数。
例如:
function promise1(){
return new Promise((resolve.reject)=>{
setTimeout(function(){
console.log('第一次输出')
resolve()
},1000)
})
}
function promise2(){
return new promise((resolve,reject)=>{
setTimeout(function(){
console.log('第二次输出')
resolve()
},2000)
})
}
//我们可以将其写成
promise1.then(function(){return promise2()}) === promise1.then(promise2)
同样,如果promise状态变为了已拒绝状态,也就是执行了reject方法,那么就会进入到后续的异常处理函数中。
function promise3(){
return new Promise((resolve,reject)=>{
let num = Math.random() * 10;
if(num > 4){
resolve(num)
}else{
reject(num)
}
})
}
let onReolved = function(num){
console.log('成功了~,数字是:',num)
}
let onRejected = function(num){
console.log('失败了~,数字是:',num)
}
//通过.then()方法的第二个参数来进行捕获异常
promise3().then(onReolved,onRejected)
//promise又一个.catch()方法,可以对异常进行捕获
promise3().catch(onRejected).then(onReolved)
//通过try catch来进行拦截状态变为reject的promise
try{
promise3().then(onReolved)
}catch(e){
onRejected(e)
}
在改变promise状态调用resolve
和reject
的时候,我们还可以给下一步.then()
函数只执行的方法进行传递参数。
总结:
promise
会有三种状态,进行中(pedding
),已完成(onFulfilled
)和已拒绝(onRejected
),状态被更改后无法继续更改。- promise构造函数接收两个参数
resolve
和reject
),执行第一个参数后会改变当前promise为已完成状态,执行第二个参数后会变为已拒绝状态。 - 通过
.then
方法,即可在上一个promise变为已完成状态时继续执行下一函数或者promise。同时通过resolve
或者reject
传入参数,可以给下一个promise或函数传入初始值。 - 已拒绝状态的promise,可以通过
.catch
方法或.then
方法的第二个参数或try catch
方法进行异常捕获。
如何将异步操作封装为promise?
例如我们将文章开头的顺序打印三个异步函数进行改造:
var fn = function(text,timer){
return new Promise((resolve)=>{
setTimeout(function(){
console.log(text)
resolve()
},timer)
})
}
//改造后
fn('hello,小明',1000).then(function(){
return fn('hello,小红',3000)
})
.then(function(){
return fn('hello,小花',2000)
})
.then(function(){
console.log('结束了~')
})
我们还可以对ajax
请求进行封装:
function ajax(url,success,fail){
var client = new XMLHttpRequest();
client.open("GET",url);
client.onreadystatechange = function(){
if(this.readyState !== 4){
return
}
if(this.status === 200){
success(this.respones)
}else{
fail(new Error(this.statusText))
}
}
client.send()
}
ajax('/ajax.json',function(){
console.log('成功~')
},function(){
console.log('失败了~')
})
//将其改造成promise
function ajax(url){
return new Promise((resolve,reject)=>{
var client = new XMLHttpRequest();
client.open("GET",url);
client.onreadystatechange = function(){
if(this.readyState !== 4){
return
}
if(this.status === 200){
resolve(this.respones)
}else{
reject(new Error(this.statusText))
}
}
client.send()
})
}
ajax('/ajax.json')
.catch(function(){
console.log('失败了~')
}).then(function(){
console.log('成功了~')
})
注意1:
var p1 = new Promise(function(resolve,reject){
//.....
})
var p2 = new Promise(function(resolve,reject){
//....
resolve(p1)
})
如果将p2的resolve
方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。
这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending
,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved
或者rejected
,那么p2的回调函数将会立刻执行。
注意2:
new Promise((resolve,reject)=>{
resolve(1)
console.log('2')
}).then(function(num){
console.log(num)
})
//2
//1
//resolve()之后的语句仍然会执行
所以为了避免不必要的错误,最好是在resolve或者reject的时候加上return语句。
new Promise((resolve,reject)=>{
return resolve(1)
console.log('2') //后面的语句不会向下执行
}).then(function(num){
console.log(num)
Promise.then()
then
方法是定义在Promise原型对象上。
then
方法的第一个参数是resolved
状态的回调函数,第二个参数是rejected
状态的回调函数。
then
方法返回的是一个新的Promise
实例。
getdata(url).then(function(data) {
return data.list;
}).then(function(data) {
// console.log(data)
});
使用then
方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
采用链式的then
,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise
对象(即有异步操作),这时后一个回调函数,就会等待该Promise
对象的状态发生变化,才会被调用。
getdata(first_url).then(
post => getdata(second_url)
).then(
data => console.log("resolved: ", data),
err => console.log("rejected: ", err)
);
第一个then
方法指定的回调函数,返回的是另一个Promise
对象。这时,第二个then
方法指定的回调函数,就会等待这个新的Promise
对象状态发生变化。如果变为resolved
,就调用第一个回调函数,如果状态变为rejected
,就调用第二个回调函数。
本文参考:
To be continued…