javascript语言是一门“单线程”的语言(javascript就像一条流水线,仅仅是一条流水线而已,要么加工,要么包装,不能同时进行多个任务和流程)。
同步和异步的差别就在于这条流水线上各个流程的执行顺序不同。
同步:提交请求 — 等待服务器处理 — 处理完毕返回,(这个期间浏览器不能干任何事)。
同步:发送方付出数据后,等待接收方响应后才发下一个数据包的通讯方式。
异步:发送方发出数据后,不等待接收方的响应,接着发送下一个数据包的通讯方式。
最基础的异步是 setTimeout 和 setInterval 函数
可以简单地理解为:可以改变程序正常执行顺序的操作就可以看成是异步操作。
demo1:
<script type="text/javascript">
console.log( "1" );
setTimeout(function() {
console.log( "2" )
}, 0 );
setTimeout(function() {
console.log( "3" )
}, 0 );
setTimeout(function() {
console.log( "4" )
}, 0 );
console.log( "5" );
</script>
以上代码会打印出 1, 5,2,3,4;
demo2:(有考察到异步、作用域、闭包)
for (var i = 0; i < 5; i++) {
setTimeOut(() => { // 同步注册回调函数到 异步的 宏任务队列。
console.log(i) // 执行此代码时,同步代码for循环已经执行完成
}, 1000)
}
第一眼看上去 大家肯定会觉得答案为:0,1, 2,3, 4;
结果当我们在控制台打印后发现答案是 5次5;
原因:
1、setTimeOut 是异步执行函数,1000后执行。
2、在for循环中的 i 每次都是执行 setTimeOut 这个函数,并没有执行里面的 function(闭包函数)。
3、setTimeOut 里面的 function 是由 setTimeOut 的定时触动的,也就是1000ms之后才执行。
4、也就是说 i 从 0 ~ 5时,一共执行了 5次 的 setTimeOut 函数,此时最后的 i 值已经是5了。
5、由于 for 的循环函数远远小于 10秒,10秒后 闭包函数开始执行时,i 值已经是5了。所以打印了5个5。
思考:
为什么把 var 换成 let ,结果输出为:0,1, 2,3, 4 ?
var 和 let 的区别?
- var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象;
- let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升;
- let 不允许在相同作用域内,重复声明同一个变量。
{
let a = 111;
var b = 222;
}
a // Uncaught ReferenceError: a is not defined
b // 222
for (var i = 0; i < 5; i++) {
console.log(i) // 0,1,2,3,4
}
console.log(i); // 5
for (let i = 0; i < 5; i++) {
console.log(i) // 0,1,2,3,4
}
console.log(i); //ReferenceError: i is not defined
如果不将 var 修改成 let 还能用什么方法输出 0,1,2,3,4 呢 ?
for(var i = 0; i < 5; i++) {
(function name(i) {
setTimeout(() => {
console.log(i)
}, i * 1000)
})(i)
}
如果想输出 0、1、2、3、4、5 ?可以用 es6 中的 Promise:
let arr = [];
const promise = (i) => new Promise((resolve) => {
setTimeout(() => {
arr.push(i);
console.log(i);
resolve();
}, 1000 * i)
});
for (var i = 0; i < 5; i++) {
arr.push(promise(i));
};
Promise.all(arr).then(() => { // Promise.all 等待所有都完成(或第一个失败)。
setTimeout(() => {
console.log(i);
}, 1000)
})
promise
es6 中的 promise 是可以解决异步问题的,而并不是promise本身是异步的。
let func = new Promise((resolve, reject) => {
// 异步操作
})
resolve: 异步操作后执行成功的回调函数。
reject: 异步操作后执行失败的回调函数。
reject用法:
// 多次执行如下代码能得到 resolved 4 或者 数字 >= 5
let func = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
var num = Math.ceil(Math.random() * 10);
if (num < 5) {
resolve(num);
} else {
reject('数字 >= 5')
}
}, 1000);
});
func.then((data) => {
console.log('resolved', data);
}, (err) => {
console.log('reject', err);
})
then 中传了两个参数,then 方法可以接收两个参数。第一个对应 resolve 的回调,第二个对应 reject 的回调。所以我们能够分别拿到他们传过来的数据。
then 的链式用法
能简化层层回调,用维护转态、传递转态的方式使得回调函数能够及时调用。
func.then(() => {
...
}).then(() => {
...
})
catch 的用法
catch 和 then 的第二个参数一样,是用来置顶reject 的回调。
catch 和 then 的第二个参数不同处:
如果执行第一个 then 参数时代码出错了,并不会卡死 js,而是会进入到这个 catch 的方法中。
func.then(() => {
// resolved
}).catch(() => {
// rejected
})
all 的用法
let Promise1 = new Promise(function(resolve, reject){})
let Promise2 = new Promise(function(resolve, reject){})
let Promise3 = new Promise(function(resolve, reject){})
let p = Promise.all([Promise1, Promise2, Promise3])
p.then(funciton(){
// 三个都成功则成功
}, function(){
// 只要有失败,则失败
})
all 可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。
race 的用法
race 函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。
var p1 = new Promise(function(resolve, reject) {
setTimeout(reject, 10, "one");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 20, "two");
});
var p3 = new Promise(function(resolve, reject) {
setTimeout(reject, 30, "three");
});
Promise.race([p1, p2,p3]).then(function(value) {
// 未被调用
console.log(value, 11111)
}, function(reason) {
console.log(reason); // "one"
// p1 更快,所以它失败了
});
捕获错误的方法 catch()
解析全部方法 all()
竞赛 race()
生成一个成功的promise resolve()
生成一个失败的promise reject()
try catch 语法:
try {
tryCode - 尝试执行代码块
}
catch(err) {
catchCode - 捕获错误的代码块
}
finally {
finallyCode - 无论 try / catch 结果如何都会执行的代码块
}