文章目录
- 一、Javascript的单线程模型
- 二、传统的异步处理
- 二、Promise
- 基本用法
- 1. promise实例生成之后,可以用then方法分别指定resolve状态和rejected状态。
- 2. 调用resolve函数和reject函数时带有参数,那么这些参数会传递给回调函数。 如果传的是值,那么在接下来的then当中就可以接收到。
- 3. 如果有多个promise对象,那么它们的状态可以互相传递,并且互相影响。
- 4. 注意:调用resolve和reject并不会终结Promise的参数函数的执行。 一般,调用resolve和reject之后,Promise的使命就完成了,后续操作都应该放在then方法里面,所以最好在它们前面加上return语句,避免意外的发生。
- 5. 多个promise一起执行。使用promise.all
- 三、Promise的应用
一、Javascript的单线程模型
在介绍promise之前,可以提及一下JavaScript最初的设计模式,这样才更好地理解 同步任务 和 异步任务
JavaScript在最初的时候就被设计为单线程模型,为什么呢?
这里举一个例子,如果同时操作dom元素渲染,如果JavaScript是并发执行的,那么可能多个线程都同时处理一个任务,之后的渲染就会出现冲突。JavaScript采用单线程模型,可以有效地控制事件的执行顺序,避免多线程处理出现的问题。
但是又因为JavaScript是单线程,一次只能同时处理一个任务,如果这个任务始终没有执行完成,那后面的任务都不能执行,那么整个系统就会处于死锁状态。所以后面引入了同步任务和异步任务的概念。
简单描述一下单线程工作原理
JavaScript通过维护主任务的执行栈和任务队列,每当接受一个请求,如果是同步事件,就在主任务的执行栈中排队执行,如果是异步任务,就会放入任务队列,然后继续接受请求。如果主任务的执行栈中的任务执行完了之后,就会去循环任务队列,任务队列遵循先进先出的顺序,有效控制了任务的执行顺序,等到该任务处理完之后,再次循环任务队列,重复操作。
二、传统的异步处理
异步setTimeout
我们先来看看setTimeout的作用。
console.log("1")
console.log("2")
console.log("3")
很容易知道上面输出 1 2 3,因为没有异步调用,所以都按顺序执行。
如果在上面的代码中加入setTimeout函数呢?
console.log("1")
setTimeout(() => {
console.log("2")
}, 1000);
console.log("3")
//输出 1 3 2
为什么呢?和上面提到的任务队列有关,当执行到setTimeout的时候,因为setTimeout是异步任务,所以将setTimeout放入任务队列,然后继续执行下面的代码,直到下面的同步任务执行完之后,开始循环任务队列,发现任务队列里面有一个setTimeout任务,然后才开始执行setTimeout任务。
没有promise的异步处理
在没有使用promise之前,我们需要在需要异步函数的函数的参数中,加入函数名。因为异步函数主要是利用了setTimeout的运行机制。会将要执行的setTimeou任务丢入任务队列,而不是直接执行。
这里我们定义dealasync函数,参数一是输入的数据,参数二是一个函数名。
我们通过setTimeout开启一个异步任务,callback函数用来处理setTimeout处理的结果,因为我们不知道setTimeout任务什么时候结束,所以干脆直接定义一个函数,只要setTimeout处理完成之后有结果,就将结果丢进callback进行处理。
输出结果为 1 3 2 ,2会间隔小段时间输出,因为要等到setTimeout处理完之后才会打印。
function dealasync(data,callback){
setTimeout(() => {
let data2 = data+1
if(typeof(callback) === 'function'){
callback(data2)
}
}, 2000);
}
console.log("1")
dealasync(1,function(r){
console.log(`${r}`)
})
console.log("3")
那么,如果出现错误怎么办?如何捕捉错误呢??
我们可以使用try…catch…将认为会出错的那部分代码丢进 try 里面,然后用 catch 来捕捉和处理错误。
function dealasync(val,success,failure){
setTimeout(() => {
try{
if(typeof(val)!=='Number'){
throw new Error("it's not belong number")
}else{
let newval = val +1
if(typeof(success) === 'function'){
success(newval)
}
}
}
catch(err){
if(typeof(failure) === 'function'){
failure(err)
}
}
}, 1000);
}
-----------------------------------以下代码执行
dealasync("1", function () {
console.log("这是数字")
}, function () {
console.log("这不是数字")
})
// 输出:这不是数字
按照这样的代码结构来书写,每一个函数里面都要写两个函数,一个为成功的回调,一个为失败的回调,如果我们在成功的回调中,再嵌套一层回调,回调依赖回调。那么回调的情况又会进一步复杂。
所以在ES6中,我们引入了Promise!!!!!简直太方便了。
二、Promise
promise是一种异步编程更好的解决办法,比上面的方案更加强大!!
- promise的优点:对象的状态不受外界影响,一旦状态改变,就不会再变。有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败),只有异步操作的结果才可以决定当前是哪一种状态。
- promise的缺点:一旦新建就会立即执行,无法中途取消。如果你要捕捉错误,你肯定要定义函数来处理,不然抛出的错误不会反应到外部。
resolve 和 reject 是改变promise对象状态的两个方法。
resolve之后,会执行成功的回调。将状态从 pending -> fulfilled
reject之后,会执行失败的回调。将状态从 pending -> rejected
比如:
//直接执行
console.log("1")
let p = new Promise((resolve,reject)=>{
resolve()
//直接执行
console.log("2")
}).then(()=>{
//丢入微任务队列
console.log("3")
})
//直接执行
console.log("4")
//输出1 2 4 3
这里会涉及到两个概念,分别为宏任务和微任务。自行百度。
执行顺序为:
console.log(“1”)
遇到promise,执行console.log(“2”)
console.log(“4”)
console.log(“3”)
promise新建后立即执行,所以在输出1后就会输出2,然后then方法指定的回调函数,将在当前脚本所有同步任务执行完后才会执行,所以3是最后输出的。
基本用法
1. promise实例生成之后,可以用then方法分别指定resolve状态和rejected状态。
和上面 没有promise异步处理中的代码相似,都会在函数的参数中指定成功和失败的处理函数。
promise.then(function(value){},function(err){})
2. 调用resolve函数和reject函数时带有参数,那么这些参数会传递给回调函数。 如果传的是值,那么在接下来的then当中就可以接收到。
let p = new Promise((resolve, reject) => {
reject("1")
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
//输出为 1
3. 如果有多个promise对象,那么它们的状态可以互相传递,并且互相影响。
不指定 a 状态:
var a = new Promise((resolve,reject)=>{
})
var b = new Promise((resolve,reject)=>{
resolve(a)
}).then(res=>{
console.log("成功")
}).catch(res=>{
console.log("失败")
})
// 输出为空
指定 a 的状态为 fulfilled。
var a = new Promise((resolve,reject)=>{
resolve()
})
var b = new Promise((resolve,reject)=>{
resolve(a)
}).then(res=>{
console.log("成功")
}).catch(res=>{
console.log("失败")
})
//输出为:成功
指定 a 的状态为 rejected。
var a = new Promise((resolve,reject)=>{
reject()
})
var b = new Promise((resolve,reject)=>{
resolve(a)
}).then(res=>{
console.log("成功")
}).catch(res=>{
console.log("失败")
})
//输出为:失败
4. 注意:调用resolve和reject并不会终结Promise的参数函数的执行。 一般,调用resolve和reject之后,Promise的使命就完成了,后续操作都应该放在then方法里面,所以最好在它们前面加上return语句,避免意外的发生。
new Promise((resolve,reject)=>{
return resolve(1)
}).then(res=>{
//后续操作都在then方法里
)
当然,Promise实例可以链式的then()调用 ,可以一直调用下去。
如果Promise的状态从pending变成rejected。那么直接跳过then语句,直接执行最后的catch方法,捕获异常。
new Promise((resolve,reject)=>{
console.log("1")
resolve()
}).then(()=>{
console.log("2")
}).then(()=>{
console.log("3")
}).then(()=>{
console.log("4")
}).catch(err=>{
console.log("err错误")
})
状态变为fulfilled的时候,会一直执行then中的方法,输出 1 2 3 4
如果把状态变为reject。我们来看看
new Promise((resolve,reject)=>{
console.log("1")
reject("1")
}).then(()=>{
console.log("2")
}).then(()=>{
console.log("3")
}).then(()=>{
console.log("4")
}).catch(()=>{
console.log("err错误")
})
输出为 1 err错误 。 直接跳过了中间多个then方法,说明当状态变为reject的时候,直接跳到最后的错误处理。
5. 多个promise一起执行。使用promise.all
多个任务同时接受,把多个promise合成同一个promise,接受多个,返回一个。r接受到的是合成多个promise resolve返回的值。
var p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve("p1")
}, 1000);
})
var p2 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve("p2")
}, 2000);
})
var p3 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve("p3")
}, 3000);
})
Promise.all([p1,p2,p3]).then((r)=>{
console.log(r)
}).catch((err)=> console.log(err.message))
三、Promise的应用
用promise对象封装的ajax请求
function ajax(url){
return new Promise((resolve,reject)=>{
let client = new XMLHttpRequest()
client.open('GET',url)
client.onreadystatechange = handler;
client.responseType = 'json'
client.setRequestHeader('Accept','application/json')
client.send()
function handler(){
if(this.readyState !==4) return;
if(this.status ===200){
resolve(this.response)
}else{
reject(new Error("JSON not found"))
}
}
})
}
ajax(url).then(res=>{}).catch(err=>{})