「极速上手TypeScript」TypeScript之Promise
一、异步编程
- 异步编程的概念:异步编程(Asynchronous, async)是相对于同步编程(Synchronous, sync)的;我们在学习传统的单线程编程中,运行的程序都是按照同步执行的,(注意 :同步不意味着所有步骤同时运行,而是指步骤在一个控制流序列中按顺序执行),而异步的概念则是不保证同步的概念,也就是说,一个异步过程的执行将不再与原有的序列有顺序关系。
简单的解释就是:同步是按照你代码的顺序执行的,而异步则不按照你代码顺序执行,异步的执行的效率会更高。
什么时候使用异步编程:在前端编程中(甚至后端有时也是这样),我们在处理一些简短、快速的操作时,例如计算 1 + 1 的结果,往往在主线程中就可以完成。主线程作为一个线程,不能够同时接受多方面的请求。所以,当一个事件没有结束时,界面将无法处理其他请求。
现在有一个按钮,如果我们设置它的 onclick 事件为一个死循环,那么当这个按钮按下,整个网页将失去响应。
为了避免这种情况的发生,我们常常用子线程来完成一些可能消耗时间足够长以至于被用户察觉的事情,比如读取一个大文件或者发出一个网络请求。因为子线程独立于主线程,所以即使出现阻塞也不会影响主线程的运行。但是子线程有一个局限:一旦发射了以后就会与主线程失去同步,我们无法确定它的结束,如果结束之后需要处理一些事情,比如处理来自服务器的信息,我们是无法将它合并到主线程中去的。
为了解决这个问题,JavaScript 中的异步操作函数往往通过回调函数来实现异步任务的结果处理。
二、 Promise
构造Promise:
现在我们构造一个Promise的类
function Promise (function(resolve, reject){ //要做的事 })
当我们遇到一些异步任务,如果是一次还好,那如果是多次呢,例如:现在我们需要分三次输出字符串,第一次间隔 1 秒,第二次间隔 4 秒,第三次间隔 3 秒,在此之前我们是这样实现的:
setTimeout(function (){ console.log('Frist') setTimeout(function (){ console.log('Second') setTimeout(function (){ console.log('Thirt') },3000) }, 4000) }, 1000)
仔细看着代码是不是很复杂,这段程序完成了我们的目的可见维护起来是一件非常繁琐的事。
那么我们现在用Promise来实现:
new Promise(function(resolve, reject){ setTimeout(function(){ console.log('Frist') resolve() }, 1000) }).then(function(){ return new Promise(function(resolve, reject){ setTimeout(function(){ console.log('Second') resolve() }, 4000) }).then(function(){ return new Promise(function(resolve, reject){ setTimeout(function(){ console.log('Thirt') resolve() }, 3000) })
其实这段代码仍然很长,而且不好理解,这里我们并不需要看懂
Promis的使用:
事例我们仍然使用上面的计时器,Promise只是一个函数,他的参数也是一个函数,
new Promise(function (resolve, reject)
而Promise函数中的参数函数中的参数同样是函数即:resolve()
和reject()
当Promise被构造时,起始函数就是异步执行,调用函数resolve(res)
表示一切正常执行,并会将res
给入下一个调用中;调用函数reject()
表示出现异常。new Promise(function (resolve, reject) { var a = 0 var b = 1 if (b === 0) { reject("Divide zero") }else { resolve(a / b) } }).then(function (value) { console.log("a / b = " + value); }).catch(function (err) { console.log(err) }).finally(function () { console.log("End") })
程序输出结果:
a / b = 0 End
这里:
function().then(function(Res)) //会将上一次调用的resolve(res)中的res给到Res
function().catch(function(Res)) //会将上一次调用的reject(res)中的res给到Res,会将我们提前处理的错误返回给Res,这样一来程序不会挂掉
但是请注意以下两点:
resolve 和 reject 的作用域只有起始函数,不包括 then 以及其他序列;
resolve 和 reject 并不能够使起始函数停止运行,别忘了 return。
这里做完整的补充:Promise 类有 .then() .catch() 和 .finally() 三个方法,这三个方法的参数都是一个函数,.then() 可以将参数中的函数添加到当前 Promise 的正常执行序列,.catch() 则是设定 Promise 的异常处理序列,.finally() 是在 Promise 执行的最后一定会执行的序列。 .then() 传入的函数会按顺序依次执行,有任何异常都会直接跳到 catch 序列:
new Promise(function (resolve, reject) {
console.log(1111)
resolve(2222)
}).then(function (value) {
console.log(value)
return 3333
}).then(function (value) {
console.log(value)
throw "An error"
}).catch(function (err) {
console.log(err)
});
输出:
1111
2222
3333
An error
三、Promise函数
- Promise计时器的实现
function print(delay, message):Promise<string>{
return new Promise((resolve, reject)=>{
setTimeout(()=>{
console.log('message')
resolve()
}, delay)
})
}
调用:
print(1000, 'Frist').then(()=>{
return print(4000, 'second').then(res=>{
return print(3000, 'Thirt').then(res=>{
consoloe.log('End')
})
})
})
输出:
"message:", "frist"
"message:", "second"
"message:", "thirt"
"end"
加法 + 乘法的回调
//返回Promise<number>类型 function add(a: number, b: number): Promise<number> { console.log('start promise') //promise的参数是一个带有两个函数为参数的函数:function: (resolve, reject) return new Promise((resolve, reject) =>{ if(b % 17 === 0){ //这里找17的模, return reject(`bad number: ${b}`) } //setTimeout(function:, timeout?)系统函数, setTimeout(()=>{ //resolve具体是什么,我们不需要了解 resolve(a + b) }, 2000) }) }
调用:
add(2, 3).then(res =>{ console.log('2 + 3', res) resolve(res) return add(res, 4).then(res =>){ console.log('2 + 3 + 4', res) } }
2 + 3, 5 2 + 3 + 4, 9
乘法:
function mul(a: number, b: number): Promise<number> { return new Promise((resolve, reject) =>{ setTimeout(()=>{ resolve(a * b) }, 3000) }) }
调用:
//(2 + 3) * 4 + 4 add(2, 3).then(res =>{ console.log(res) return mul(res, 4).then(res =>{ console.log(res) return add(res, 5).then(res =>{ console.log('(2 + 3) * 4 + 4', res) }).catch(err =>{ console.log('cauht err') }) }) })
输出:
5 20 (2 + 3) * 4 + 4, 24
四、Promise处理多任务
我们经常在网络中,会同时处理多个任务,这也是Promise的真正意义,现在我们来看看简单的处理多任务:Promise.all()
方法会返回相应操作的结果,并且以数组的形式返回
//处理多个请求
//(2 + 3) * (4 + 4) * (20 + 21)
//Promise.all()返回一个对应参数数量的数组
Promise.all([add(2, 3), add(4, 4)]).then(res =>{
res[0], res[1] //const [a, b, c] = res
console.log(res[0], res[1])
return mul(res[0], res[1], ).then(res =>{
console.log('(2 + 3) * (4 + 4)=',res)
})
})
输出:
(2 + 3) * (4 + 4)=, 40
这里另外介绍一个方法:Promise.race()
传入多个参数,只返回一个结果并且是运行最快的结果
Promise.race([add(2, 3), add(4, 4)]).then(res =>{
console.log(res)
})
输出:
5
五、async/awai语法糖
在TS中我们可以使用async/awai
来简化我们在上面的代码,让我们的代码更为直观。
这是我们之前的做法:
//(2 + 3) * 4 + 4
add(2, 3).then(res =>{
console.log(res)
return mul(res, 4).then(res =>{
console.log(res)
return add(res, 5).then(res =>{
console.log('(2 + 3) * 4 + 4', res)
}).catch(err =>{
console.log('cauht err')
})
})
})
我们使用async/awai
后是这样的
//(2 + 3) * (3 + 4)
async function calc(){
try{
const [a, b] = await Promise.all([add(2, 3), add(3, 4)])
console.log('2 + 3:', a)
console.log('3 + 4:', b)
return await mul(a, b)
}catch(err){
console.log('catch err', err)
return undefined
}
}
这里需要注意的是:
async function func_name(){
await ...
}
async
需要在函数定义的字段前使用await
必须在函数中使用
本作品采用《CC 协议》,转载必须注明作者和本文链接