promise 和 async/await
callback hell
看一个例子,要求代码实现:
等待2秒后执行操作1,完成后再等待2秒执行操作2 ,完成后再等待2秒执行操作3 ,
如果是 Python 语言代码是这样写的
import time
print('这里是等待2秒前的代码')
time.sleep(2) # 等待2秒
print('这里是执行操作1代码')
time.sleep(2) # 等待2秒
print('这里是执行操作2代码')
time.sleep(2) # 等待2秒
print('这里是执行操作3代码')
而 js是异步架构,需要使用回调
代码是这样写的
setTimeout(
function() {
console.log("这里是执行操作1代码");
setTimeout(
function() {
console.log("这里是执行操作2代码");
setTimeout(
function() {
console.log("这里是执行操作3代码");
},
2000
)
},
2000
)
},
2000
)
console.log("这里是设置后的代码");
很容易搞错匹配的符号和参数。
可见,回调这种写法也有缺点,主要是使得代码看起来凌乱复杂了。
这种多层的回调也被称为 戏称为 回调地狱 (callback hell)
为了解决这个问题, ES6 引进了 promise 和 async、await 。
创建 Promise
js中有 Promise构造函数 ,用来创建 Promise 类型的对象
常见的用法是,传入一个函数作为该构造函数的参数,如下
var p = new Promise(
function(resolve, reject) {
console.log('执行函数1')
resolve(1);
}
)
console.log(p)
var p = new Promise(
function(resolve, reject) {
console.log('执行函数1')
reject('错误原因:服务端返回异常');
}
)
console.log(p)
var p = new Promise(
function(resolve, reject) {
console.log('执行函数2')
setTimeout(() => resolve(2), 1000);
}
)
console.log(p)
var p = new Promise(
function(resolve, reject) {
console.log('执行函数3')
setTimeout(() => resolve({name:'白月黑羽'}), 1000);
}
)
console.log(p)
var p = new Promise(
function(resolve, reject) {
console.log('执行函数4')
setTimeout(() => reject(2), 1000);
}
)
console.log(p)
里面的这个参数函数,被称之为 executor, 这里我们暂时叫 执行函数
执行函数 有两个参数 resolve, reject
resolve, reject也是函数,是由js引擎实现的,在回调执行 执行函数时传入
执行函数 作为构造函数的参数, 在创建Promise对象后,会被立即执行
在执行执行函数过程中:
-
如果还没有调用 resolve,或者 reject,那么这个promise 还 没有结果 , 这个promise的状态就是 pending
-
如果调用了 resolve, 那么这个promise结果就是 成功 ,resolve的参数 就作为这个 promise对象 的 成功结果 , 这个promise的状态就是 fulfilled
-
如果调用了 reject, 那么这个promise结果就是 失败 ,reject 的参数 就作为这个 promise对象 的 失败结果 , 这个promise的状态就是 rejected
这个两个函数参数 resolve, reject 其实是包含了创建的promise对象的闭包。
所以调用时,自然知道应该设置 哪个promise对象的 resolve、reject结果
这样,不管执行函数里面的 resolve、reject函数 是立即执行、还是延期执行(后续的底层循环触发回调),都能设置好promise对象的结果
Promise的then方法
当一个 promise 成功了, 或者失败了, 就是有了最终的结果,我们的代码怎么对这个最终的结果进行处理呢?
就是通过这个 promise的 then 方法
参数 - 处理函数
then 方法接受 两个函数参数 ,
第1个函数参数,称之为 onFulfilled ,是用来处理 成功结果的函数。接受的参数,自然就是 primise resolve 的结果
第2个函数参数,称之为 onRejected ,是用来处理 失败结果的函数。接受的参数,自然就是 primise reject 的结果
如下
// 示例1
new Promise(
function(resolve, reject) {
setTimeout(() => resolve(2), 1000);
}
)
.then(
// 第1个参数函数,onFulfilled,处理成功结果
function(result) {
console.log(result);
},
// 第2个参数函数,onRejected,处理失败结果
function(reason) {
console.log(reason);
}
)
// 示例2
new Promise(
function(resolve, reject) {
setTimeout(() => reject({msg:'参数错误', details:'用户名不存在'}), 1000);
}
)
.then(
// onFulfilled
function(result) {
console.log(result);
},
// onRejected
function(reason) {
console.log(reason);
}
)
// 示例3
new Promise(
function(resolve, reject) {
resolve(1)
}
)
.then(
// onFulfilled
function(result) {
console.log(result);
},
// onRejected
function(reason) {
console.log(reason);
}
)
执行到 .then(...) 时 ,Promise对象添加了2个新的属性,onFulfilled, onRejected,分别对应resolve时执行的函数、 reject时执行的函数
这时,
-
如果Promise对象已经有结果了
就会根据结果是成功还是失败,相应的执行 onFulfilled 或者 onRejected 属性对应的处理函数
-
如果Promise对象还没有结果
不做任何处理,等后续有结果了(调用了resolve或者reject),根据结果是成功还是失败,相应的执行 onFulfilled 或者 onRejected 属性对应的处理函数
这里有个注意点:
当Promise对象被调用了 resolve或者reject有结果了, 并不会立即调用 对应的 onFulfilled 或者 onRejected 属性对应的处理函数。
而是安排在下个js底层循环调用
所以,运行下面代码
new Promise(
function(resolve, reject) {
console.log('1');
resolve('b')
}
)
.then(
function(resolve, reject) {
console.log('2');
}
)
console.log('3')
结果是:
1
3
2
而不是
1
2
3
返回值 - Promise
特别要注意的是:
then方法的 的返回值 也是一个promise ,为了方便表述,我们称之为 then promise ,then属于的promise 称之为 原promise
当 原promise 有了结果后, 根据成功还是失败,会选择它then的两个参数 处理函数 之一执行,
执行 处理函数 的过程中,如果
-
如果
return了一个数据这种情况也包括没有写return语句, 其实就是 return undefined
then promise就成功了,等于是 resolve 这个返回的对象。比如:
var p = new Promise( function(resolve, reject) { setTimeout(() => resolve({code:200, msg:'调用成功'}), 5000); } ) .then( function(result) { if (result.code === 200) return 'then promise 的新结果成功' else throw 'then promise 的新结果失败' } ) console.log(p)浏览器console运行结果显示的是
then promise,如下Promise {<pending>} [[Prototype]]: Promise [[PromiseState]]: "fulfilled" [[PromiseResult]]: "then promise 的新结果成功" -
如果
throw了一个数据then promise就失败了,等于是 reject 这个抛出的对象。比如:
var p = new Promise( function(resolve, reject) { setTimeout(() => resolve({code:400, msg:'调用失败'}), 5000); } ) .then( function(result) { if (result.code === 200) return 'then promise 的新结果成功' else throw 'then promise 的新结果失败' } ) console.log(p)浏览器console运行结果显示的是
then promise,如下Promise {<pending>} [[Prototype]]: Promise [[PromiseState]]: "rejected" [[PromiseResult]]: "then promise 的新结果失败" Uncaught (in promise) then promise 的新结果失败 -
如果
return了一个 fulfilled Promisethen promise就成功了,等于是 resolve 这个返回的对象。比如:
var p = new Promise( function(resolve, reject) { resolve(1)} ) .then( function(result) { return new Promise( function(resolve, reject) {resolve(result+1)} ) } ) -
如果
return了一个 rejected 的 Promisethen promise就失败了,等于是 reject 这个参数对象。比如:
var p = new Promise( function(resolve, reject) { resolve(1)} ) .then( function(result) { return new Promise( function(resolve, reject) {reject(result+1)} ) } ) -
如果
return了一个 pending Promisethen promise当前没有结果, 其最终结果和后续 这个 return promise 的结果一样,比如:
var p = new Promise( function(resolve, reject) { resolve(1) } ) .then( function(result) { return new Promise( function(resolve, reject) { setTimeout(() => resolve(result+1), 1000) } ) } )或者
var p = new Promise( function(resolve, reject) { resolve(1) } ) .then( function(result) { return new Promise( function(resolve, reject) { setTimeout(() => reject(result+1), 1000) } ) } )
then 方法的详细描述可以参考MDN文档
Promise 链
既然then 返回的也是一个promise,那么自然也可以对它调用 then 方法了。
第2次调用then返回的也是promise,自然也可以继续对它调用 then 方法了,
依次类推, 可以一直调用下去,形成一个 Promise 链
直到 某个环节promise的 then 处理函数, 处理完这个promise的结果后,不需要再 return 新的结果 供后续处理了。
处理函数不返回任何结果(也不throw结果),虽然对应的then promise 还是会有一个值为undefined的成功结果,但是没有有处理它的意义, 后续也就不需要继续再加 then 方法了。
示例代码如下
var p = new Promise(function(resolve, reject) {
setTimeout(() => resolve(2), 1000);
})
.then(function(result) {
console.log(result);
return result * 2;
})
.then(function(result) {
console.log(result);
return result * 2;
})
.then(function(result) {
console.log(result);
})
console.log('后续代码执行')
执行结果是
后续代码执行
2
4
8
说明了Promise链所有 then 里面的代码都是后面的事件循环执行的
所有 then 里面的代码 都是在 promise链后面的代码执行 之后 再执行的
当然,then里面的函数可以是另外的延时处理,比如
new Promise( (r,j) => {setTimeout(() => r(2), 1000) })
.then(function(ret) {
console.log(ret);
return new Promise((r,j)=>{setTimeout(() => r(ret*2), 1000);})
})
.then(function(ret) {
console.log(ret);
return new Promise((r,j)=>{setTimeout(() => r(ret*2), 1000);})
})
.then(function(ret) {
console.log(ret);
})
这样就避免了前面说的层层缩进的回调地狱的问题
错误处理
参考 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#error_propagation
primise 如果reject,结果失败,本质上就是会抛出错误,如果then没定义 第2个参数函数,像下面这样
new Promise(
function(resolve, reject) {
reject({msg:'参数错误'});
}
)
.then(
// 第1个参数函数,处理成功结果
function(result) {
console.log(result);
},
)
console.log('执行后续代码')
就会在promise reject 时,抛出错误。
但是这个错误不会中止影响下面的这条语句的执行
console.log('执行后续代码')
为什么?
因为 前面讲过, Promise对象resolve,reject了,对应的处理会在下个js底层循环执行
而当前继续 new Promise 后面的代码。
要对可能出现的错误情况做处理,
就需要 定义 then的 第2个参数函数 进行错误处理, 或者, 调用 catch 方法,
就像错误捕获那样,promise 链上 任意一个环节出现reject,
js引擎都会:
-
从当前这个环节开始
-
顺着promise 链向后
-
看有没有 catch 处理错误,或者 then 定义了 onRejected 的处理函数
如果,某个then 已经有错误处理,并且resolve当前then Promise, 后续继续正常处理
如下
new Promise(function(resolve, reject) {
setTimeout(() => reject(1), 1000);
})
.then(function(result) {
console.log('then1-正确处理',result);
return result * 2;
})
.then(
function(result) {
console.log('then2-正确处理',result);
return result * 2;
},
// 这里处理了
function(result) {
console.log('then2-错误处理',result);
return result * 2;
}
)
.then(function(result) {
console.log('then3-正确处理',result);
})
.catch(reason =>{
console.log('catch中捕获的错误:',reason)
})
如果,一直没有 then 有错误处理,就会由最终的catch方法捕获
如下
new Promise(function(resolve, reject) {
setTimeout(() => reject(1), 1000);
})
.then(function(result) {
console.log('then1-正确处理',result);
return result * 2;
})
.then(function(result) {
console.log('then2-正确处理',result);
return result * 2;
})
.then(function(result) {
console.log('then3-正确处理',result);
})
.catch(reason =>{
console.log('catch中捕获的错误:',reason)
})
async await
async 和 await 是ES6引进的异步关键字, 是 Promise 的语法糖
让异步处理写起来更简单
function foo() {
// doSomething()返回一个promise
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);
}
可以写成
async function foo() {
try {
const result = await doSomething();
const newResult = await doSomethingElse(result);
const finalResult = await doThirdThing(newResult);
console.log(`Got the final result: ${finalResult}`);
} catch(error) {
failureCallback(error);
}
}
函数定义时,前面 加上 async ,就是声明,这是一个 异步函数
只要函数内部要使用 await ,必须定义为异步函数
await 后面跟着的,通常是一个 promise对象, 比如返回 promise 的函数调用,
执行到await这里时,其实js引擎会从代码处返回 底层js循环,继续进行其它处理。
等 await的 promise 被resolve了,resolve的值作为await 的返回值,
然后再接着执行后面的代码。
看一个具体的例子
前面的promise示例,如果放在函数中调用,如下
function delayCall(){
new Promise( (r,j) => {setTimeout(() => r(2), 1000) })
.then(function(ret) {
console.log(ret);
return new Promise((r,j)=>{setTimeout(() => r(ret*2), 1000);})
})
.then(function(ret) {
console.log(ret);
return new Promise((r,j)=>{setTimeout(() => r(ret*2), 1000);})
})
.then(function(ret) {
console.log(ret);
})
}
delayCall()
使用 async,await 可以写成
async function delayCall(){
var ret = await new Promise( (r,j)=>{setTimeout(()=>r(2), 1000) })
console.log(ret);
var ret = await new Promise( (r,j)=>{setTimeout(()=>r(ret*2), 1000) })
console.log(ret);
var ret = await new Promise( (r,j)=>{setTimeout(()=>r(ret*2), 1000) })
console.log(ret);
}
delayCall()
效果相同,但是写法更容易理解。
ES2020支持了顶层代码直接使用 await,目前主流浏览器都已经支持
如下
var ret = await new Promise( (r,j)=>{setTimeout(()=>r(2), 1000) })
console.log(ret);
var ret = await new Promise( (r,j)=>{setTimeout(()=>r(ret*2), 1000) })
console.log(ret);
var ret = await new Promise( (r,j)=>{setTimeout(()=>r(ret*2), 1000) })
console.log(ret);
注意:顶层代码直接使用 await, 一定要放在模块化的js代码中。
如果是直接嵌入html,要加 type="module" 声明。
比如
<script type="module">
var ret = await new Promise( (r,j)=>{setTimeout(()=>r(2), 1000) })
console.log(ret);
</script>
否则会报错:await is only valid in async functions and the top level bodies of modules
如果await 后面的不是Promise对象,而是一个其它数据,
js引擎会把它转化为resolve结果为该值的Promise对象
如下
var ret = await 'HELLO'
console.log(ret)
调用异步函数,如果前面没有await, 那就只是调用,不等待其异步的返回值
如下
async function f1() {
console.log('f1')
return 1;
}
async function f2() {
console.log('f2-1')
ret = await f1()
console.log('f2-2')
}
console.log('m1')
f2() // 不等待f2 resolve
console.log('m2')
运行结果是
m1
f2-1
f1
m2
f2-2
对比await等待返回值的代码如下
async function f1() {
console.log('f1')
return 1;
}
async function f2() {
console.log('f2-1')
ret = await f1()
console.log('f2-2')
}
console.log('m1')
await f2() // 等待f2 resolve
console.log('m2')
运行结果是
m1
f2-1
f1
f2-2
m2