异步编程Promise绝对是ECMAscript2015(ES6)中的一个难点。如何打败它?
第一步:为什么要用Promise?
那肯定是我们之前异步编程方式存在着种种问题
1.回调地狱,过多的嵌套回调让代码毫无可读性,不利于调试和维护。
就行下面这种情况,每次发送Ajax请求都需要依赖上一个请求返回的结果,最终就会一直嵌套下去,令人作呕。
$.ajax({
//一些请求信息
success: (data) => {
$.ajax({
url: data.src
//等等
success: (data) => {
//some code
}
})
}
})
2.异步产生的错误无法被try catch捕捉到。
try{
setTimeout(() => {
console.log(a); //没有定义过的变量a
}, 1000)
}.catch(error){
console.log(error); //控制台报错,并不会输出错误信息
}
3.如果一个函数的执行依赖了多个异步返回的结果,难以处理。
大家可以想象一下,加入我要执行函数A,但是它需要10个异步函数返回的结果。怎么处理?可能我们需要定义一个全局变量为一个数组arr,每一个异步函数返回结果后push进去,当数组长度为10时把数组arr作为A的实参去执行A。但是我们又不确定什么时候arr的长度会变成10,总之很麻烦。
第二步:什么是Promise?
Promise汉语直译承诺,其实它是一种解决异步编程问题的模式。他并没消除回调,只是让回调变得更加可控、灵活。
new Promise((res, rej) => {
res(1)
}).then((data) => {
console.log(data) //打印 1
})
这就是一个简单的Promise用法,Promise是ES6提供给我们的构造函数,new 返回Promise对象,我们必须传入一个函数作为参数(不然报错),这个函数中可以写入两个参数(也是两个函数,命名随意,通常第一个传resolve,第二个为reject)。
先来说一说Promise的模式:两个阶段,三个状态
结合上边这一张图和以下代码,
var pro1 = new Promise((resolve, reject) => {
//未决阶段的一下操作代码
})
console.log(pro1); // 打印Promise{<pending>}
var pro2 = new Promise((resolve, reject) => {
resolve('ok');
})
console.log(pro2); // 打印Promise{<resolved>:"ok"}
var pro3 = new Promise((resolve, reject) => {
reject('no');
})
console.log(pro3); // 打印Promise{<rejected>:"no"}
var pro4 = new Promise((resolve, reject) => {
throw('error');
})
console.log(pro4); // 打印Promise{<rejected>:"error"}
我们发现当我们刚new完的Promise对象处于未决阶段(unsettled)pending状态,当我们执行resolve将会把Promise推向已决阶段(settled)的resolved状态,当我们执行reject将Promise推向已决阶段rejected状态。哪推向不同的阶段由于什么用呢?
其实可以简单理解为Promise进入已决阶段,就要进行一些异步操作了。
接下来,我们就要通过Promise提供的API为他注册一点异步调用的函数。
介绍一下我们用的最多的API,then(),它可以传入两个函数作为参数,如果我们的Promise进入resolved状态(也就是调用resolve函数)就会调用第一个函数,同理,rejected状态就调用第二个函数,函数可以接受一个参数就是我们调用resolve或reject传过来的数据,或者报错信息
new Promise((res, rej) => {
res(1)
console.log(2)
rej(3)
}).then((data) => {
console.log(data)
},(error) => {
console.log(error)
})
console.log(4); //打印 2, 4, 1
说到这里有几个需要注意的点,不知道小伙伴们发现没有,
1.未决阶段的代码执行是同步的,会立即执行。
我们发现上边代码中2再4之前打印出来,未决阶段没有异步操作,都是同步一行一行执行的。1在最后打印,也就是说我们通过执行res(1)把Promise推向了已决阶段,他接下来所进行的操作都是异步的,要加入微任务队列等待JS执行栈(call stack)为空时再加入栈中执行。这里是一些事件循环的知识了
2.一旦状态推向了已决阶段,无法再对状态做任何更改。
上边代码中执行了res(1),Promise将推向resolved状态,再执行rej(3)状态也不会再更改。
第三步:Promise的串联操作
与小伙伴发现,说了半天,虽然说Promise解决了异步操作错误无法捕获的问题,但并没有解决回调地狱。这就要用到Promise的串联了
var pro = new Promise((res, rej) => {
res("sf")
}).then((data) => {
//some code
})
console.log(pro); //打印 Promise {<pending>}
看,Promise对象调用then后有返回了一个全新的Promise对象,那么我们是不是就可以一直then一直爽?
我们Promise对象调用then()后会把执行完返回结果,作为全新的Promise内的resolve的参数进行执行,从而又将新Promise推向了已决阶段,然后我们就需要使用then为它注册异步操作。然后...是不是熟悉的味道?
var pro = new Promise((res, rej) => {
throw("error")
}).then(() => {} ,(data) => {
return `my${data}`
})
pro.then((data) => {
console.log(data) //打印 "myerror"
})
你会发现.then()操作之后好像在调用API时,它在内部帮我们做了好多事情。接下让我们探索一下
var pro1 = new Promise((res, rej) => {
res("here")
})
var pro2 = new Promise((res, rej) => {
res("there")
})
pro1.then((data) => {
return pro2
}).then((data) => {
console.log(data) //打印 "there"
})
这一段代码非常好理解,就是我们帮Promise对象把调用then之后返回一个新Promise对象的事情给做了
var pro = new Promise((res, rej) => {
res("ok")
})
pro.then().then().then((data) => {
console.log(data) //打印 "ok"
})
这一段代码我们发现每一次then。内部都
then((data) => {
return new Promise((res, rej) => {
res(data);
})
})
帮我们这样操作了一把。
Promise还有其他一些API,这里就不多结束,道理都差不多
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。