异步操作前置知识
jS是单线程的
就是同一时间只能处理一个任务。就类似生活中的去超市排队结账,正常情况下,以为收银员只能为一位顾客结账,其他顾客需要在后面排队等候。
为什么js是单线程的?作为浏览器脚本语言,javascript的主要用途是与用户互动,以及操作DOM,
这决定了它只能是单线程,否则会带很复杂的同步问题。比如,假定javascript同时有两个线程,
一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
单线程就意味着,所有任务都需要排队,前一个任务结束,才能执行后一个任务。如果前一个任务耗
时很长,那么后一个任务就不得不一直等待,于是乎,js设计师们就把所有的任务分成两类,同步和异步。
同步:只有前一个任务执行完毕,才能执行后一个任务。
异步:当同步任务执行到某个WebAPI时,就会触发异步操作,此时浏览器就会单独开线程去处理这些
异步任务
同步
const a = 2
const b = 3
console.log(a+ b)
异步
setTimeout(()=>{
console.log(a+ b)
},1000)
请思考下面的输出结果是什么?
console.log(1)
setTimeout(()=>{ //异步任务,放入任务队列中
console.log(2)
},0)
console.log(3)
//输出结果 1,3,2
下图说明了同步任务和异步任务的执行过程
![](https://img.haomeiwen.com/i5601500/64b0e9b6342cbad9.png)
Ajax原理
Ajax即'Asyncchronous Javascript And XML'(即异步javascript和XML),是指一种创建交互式,快速动态
网页应用的网页开发技术,无需重新加载整个网页的情况下,能够更新部分网页的技术。
通过在后台与服务器进行少量数据交换,Ajax可以使网页异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
//创建XMLHttpRequest对象
const url = 'http://www.baidu.com'
let xmlhttp
if(window.XMLHttpRequest){ //code for IE7+,Firefox,Chrom,Opera,Safari
xmlhttp = new XMLHttpRequest()
}else{
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
}
//发送请求
xmlhttp.open('GET',url,true)
xmlhttp.send()
//服务端相应
xmlhttp.onreadystatechaneg = function(){
if(xmlhttp.readyState == 4 && xmlhttp.status == 200){
let obj = JSON.parse(xmlhttp.responseText)
}
}
Callback Hell
JavaScipt 中的许多操作都是异步的,我们把上面的Ajax封装成一个函数:
function ajax(url, callback) {
let xmlhttp
if (window.XMLHttpRequest) { // code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp = new XMLHttpRequest()
} else { // code for IE6, IE5
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP")
}
// 发送请求
xmlhttp.open("GET", url, true)
xmlhttp.send()
// 服务端响应
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
// console.log(xmlhttp.responseText)
let obj = JSON.parse(xmlhttp.responseText)
callback(obj)
}
}
}
a.json:
{
"a": "我是A"
}
b.json:
{
"b": "我是B"
}
c.json:
{
"c": "我是C"
}
我们可以像这样使用:
// 加载并执行脚本
ajax('/static/a.json')
函数是异步调用的,因为操作不是立即完成的,而是之后才会完成。
ajax('/static/a.json')
// 下面的代码不会等到ajax执行完才执行
// ...
这个过程大家并不陌生,可是如果在回调之后再回调呢?
ajax('static/a.json', res => {
console.log(res)
ajax('static/b.json', res => {
console.log(res)
ajax('static/c.json', res => {
console.log(res)
})
})
})
如果嵌套变多,代码层次就会变深,维护难度也随之增加。
这就被称为 “回调地狱” 或者“回调深渊”。
基本语法
Promise 就是为了解决“回调地狱”问题的,它可以将异步操作的处理变得很优雅。
回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象promise可以支持多个并发的请求,获取并发请求中的数据这个promise可以解决异步的问题,
本身不能说promise是异步的。
创建Promise实例。
const promise = new Promise(function(resolve, reject) {
// ... some code
if ( /* 异步操作成功 */ ) {
resolve(value)
} else {
reject(error)
}
})
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
- 处理结果正常的话,调用resolve(处理结果值),将Promise对象的状态从“未完成”变为 “成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果, 作为参数传递出去
2.处理结果错误的话,调用reject(Error对象),将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) {
// success
}, function(error) {
// failure
})
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
下面是一个Promise对象的简单例子。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。
Promise 新建后就会立即执行。
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
上面代码中,Promise 新建后立即执行,所以首先输出的是Promise。
然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,
所以resolved最后输出。
一个栗子:
var promise = new Promise(function (resolve, reject) {
console.log("good");
var a = 10;
var b = a + 25;
if ( b === 35 ) {
// 一旦异步执行成功,我们就调用内置的 resolve函数,将pending状态转化为resolved,并且传入我们希望传出的执行成功后的结果。
// 注意: 这里一旦状态转变,那么后面就一定会调用then方法中的第一个参数的函数,然后将我们传入给resolve的参数传给then方法中的第一个方法作为参数,我们就可以在then的第一个方法中使用了。
resolve(b);
} else {
reject("异步执行失败");
}
});
promise.then(function (value) {
console.log("异步执行成功,输出执行结果:" + value);
}, function (error) {
console.log("异步执行失败,输出执行结果:" + error);
});
promise应用的一个栗子:
getUserInfo () {
return new Promise((resolve, reject) => {
getInfo().then(rsp => {
this.loading = false
if (rsp.ret === 0)
resolve(res) //异步执行成功,调用内置的resolve函数,将pending状态转化成resolved,并且传入我们希望传出的执行成功后的结果
// 注意: 这里一旦状态转变,那么后面就一定会调用then方法中的第一个参数的函数,然后将我们传入给resolve的参数传给then方法中的第一个方法作为参数,我们就可以在then的第一个方法中使用了。
return
}
reject(rsp) //异步执行失败,调用内置的reject函数,将pending状态转化成fulfilled,并且传入我们希望传出的执行成功后的结果
// 这里一旦状态转变,那么后面就一定会调用then方法中的第2个参数的函数,然后将我们传入给reject的参数传给then方法中的第2个方法作为参数,我们就可以在then的第2个方法中使用了。
})
})
}
//调用
getUserInfo().then(function (val) {
console.log("异步执行成功,输出执行结果:" + val);
}, function (error) {
console.log("异步执行失败,输出执行结果:" + error);
});
TIP
实际上 Promise 用起来还是比较简单的,是不是可以动手试一试去封装自己业务中应用到的异步操作了(之前用回调写的)?
在这里必须说明下 Promise 内部是有状态的(pending、fulfilled、rejected),Promise 对象根据状态来确定执行哪个方法。Promise 在实例化的时候状态是默认 pending 的,当异步操作是完成的,状态会被修改为 fulfilled,如果异步操作遇到异常,状态会被修改为 rejected,可以通过下图来看下状态的走向
![](https://img.haomeiwen.com/i5601500/1a87052a0e426c0a.png)
注意
状态转化是单向的,不可逆转,已经确定的状态(fulfilled/rejected)无法转回初始状态(pending),而且只能是从 pending 到 fulfilled 或者 rejected
网友评论