目录
前言
弄清楚这个问题之前,首先我们得搞清楚JS里面什么是同步,什么是异步,这两者有什么区别?
JS是单线程的,负责解释和执行JS的代码的线程只有一个,称之为主线程。单线程意味着前一个任务结束,才会执行后一个任务。其实实际上还有其他线程,例如工作线程。
同步:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。如果在函数A返回的时候,调用者就能够得到预期的结果,那么这个函数就是同步的。
异步:不进入主线程,而是进入任务队列的,通过任务队列通知主线程,某个异步任务可以执行。如果在函数返回的时候,调用者不能马上得到预期的结果,那么这个函数就是异步。例如常用Ajax请求。就是典型的异步函数。
我们分成执行栈,消息队列,任务队列。
工作线程在异步操作完成后,将消息放到消息队列,主线程通过事件循环去取消息。
任务队列是一个事件的队列。所有任务都是在主线程上执行,形成一个执行栈。主线程代码执行完后,才会去执行任务队列中的事件,只要执行栈一清空,任务队列的第一事件就自动进入主线程。比如定时器,主线程要检查一下执行事件,只有到了规定的时间,才能返回主线程。
以下为同步代码
<script>
// 同步 执行顺序符合代码的顺序
console.log("我是第一个div的盒子");
console.log("我是第二个div的盒子");
console.log("我是第三个div的盒子");
console.log("我是第四个div的盒子");
console.log("我是第五个div的盒子");
</script>
以下为异步代码
<script>
// 异步测试 setTimeout() 在指定的毫秒数后执行某些操作
console.log("我是第一个div盒子");
setTimeout(function(){
console.log("我是第二个div盒子");
},3000);
console.log("我是第三个div盒子");
</script>
最典型的异步操作就是setTimeOut()和setTimeInterval() 他们可以控制JS的执行顺序。可以使用clearTimeOut()来清除定时器,在指定的毫秒数后,将定时任务处理函数添加到执行队列的队尾。
setInterval方法用于每隔多少时间执行某些操作,可以使用clearInterval来清除定时,按照指定的周期,将定时任务处理函数添加到执行队列的队尾。
1callback的语义和用法
我们来回顾几个经典的回调函数代码。
以下是Geogle的解释, 非常清晰简明
A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.
$.get('url', function(data) {
alert("success");
})
$('#btnDelete').click(function() {
alert("success");
})
var fruits = ["A", "B", "C"];
var arr = []
fruits.forEach(function(element) {
element += "zz"
arr.push(element)
})
function getNodes(params, callback) {
var list = JSON.stringify(params)
typeof(callback) === 'function' && callback(list);
}
getNodes('[1,2,3]', function(nodes) {
//拿到nodes后用它去做一些其他操作
})
以上几个分别是调用Ajax 点击事件的回调函数 数组中遍历 的异步回调函数。最后一个就是普通的同步回调的例子。所以回调与同步和异步并没有什么区别。回调只是一种实现方式,既可以同步回调,也可以异步回调,还可以有事件处理回调和延迟函数回调。
2promise的语义和用法
网上讲的有很多,但自己的思路还是比较乱,所以在此稍微总结一下,并且表示自己的看法
Promise通常用于更容易处理异步操作或阻塞代码,比如文件操作,API操作,IO调用。这些异步操作的启动发生在执行函数中,如果异步操作成功,则通过Promise的创建者调用resolve函数返回预期结果。同样,如果调用失败,则通过reject函数传递错误具体信息。
首先我们来搞清楚Promise中的状态,promise只有三种状态pending,fulfilled,rejected后面两种称为settled resolved是指promise已经settled或者已经使用另一个promise(B)来resolve了,此时promise的状态由B来决定。
一旦状态改变就是从pending到resolved或者从pending到rejected,只要这两种情况发生,状态就凝固了。如果一切正常,则调用resolve 否则调用reject。
var myFirstPromise = new Promise(function(resolve, reject){
//当异步代码执行成功时,我们才会调用resolve(...),
//当异步代码失败时就会调用reject(...)
//在本例中,我们使用setTimeout(...)来模拟异步代码,
//实际编码时可能是XHR请求或是HTML5的一些API方法.
setTimeout(function(){
resolve("成功!"); //代码正常执行!
}, 250);
});
myFirstPromise.then(function(successMessage){
//successMessage的值是上面调用resolve(...)方法传入的值.
//successMessage参数不一定非要是字符串类型,这里只是举个例子
document.write("Yay! " + successMessage);
});
对于已经实例化的promise对象可以调用promise.then对象,传递方法作为回调。最常规的写法是
promise.then(onFulfilled).catch(onRejected)
function ajax(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
var URL = "/try/ajax/testpromise.php";
ajax(URL).then(function onFulfilled(value){
document.write('内容是:' + value);
}).catch(function onRejected(error){
document.write('错误:' + error);
});
上述代码很好理解,就是发起Ajax请求时,如果请求成功就是reject如果请求失败那么就抛出异常,并且在函数类声明这个异常。
function greet(){
var promise = new Promise(function(resolve,reject){
var greet = "hello world";
resolve(greet);
});
return promise;
}
var p = greet().then(v=>{
console.log(v);
})
console.log(p);
//Promise { <pending> }
//hello world
-----------------------
greet().then(v=>{
console.log(v+1);
return v;
})
.then(v=>{
console.log(v+2);
return v;
})
.then(v=>{
console.log(v+3);
})
//hello world1
//hello world2
//hello world3
上述代码的含义就是将promise作为函数的返回值,然后调用。先输出promise,并且其处于pending状态,并且promise执行then之后还是Promise,所以就根据这一特性,不断的链式调用回调函数。
all方法提供了并行执行异步操作的能力,在all中所有异步操作结束后才执行回调
function p1(){
var promise1 = new Promise(function(resolve,reject){
console.log("p1的第一条输出语句");
console.log("p1的第二条输出语句");
resolve("p1完成");
})
return promise1;
}
function p2(){
var promise2 = new Promise(function(resolve,reject){
console.log("p2的第一条输出语句");
setTimeout(()=>{console.log("p2的第二条输出语句");resolve("p2完成")},2000);
})
return promise2;
}
function p3(){
var promise3 = new Promise(function(resolve,reject){
console.log("p3的第一条输出语句");
console.log("p3的第二条输出语句");
resolve("p3完成")
});
return promise3;
}
Promise.all([p1(),p2(),p3()]).then(function(data){
console.log(data);
})
p1的第一条输出语句
p1的第二条输出语句
p2的第一条输出语句
p3的第一条输出语句
p3的第二条输出语句
p2的第二条输出语句
[ 'p1完成', 'p2完成', 'p3完成' ]
等到定时器里面的执行完毕后,后面的.then方法才会执行
race的用法,在all中的回调函数,等到第一个Promise改变状态就开始执行回调函数
p1的第一条输出语句
p1的第二条输出语句
p2的第一条输出语句
p3的第一条输出语句
p3的第二条输出语句
p1完成
p2的第二条输出语句
说明当执行then方法时,只有第一个promise的状态改变了。
最后再补充一个小的知识,不要在promise后面执行一些依赖promise改变才能执行的代码,就比如我想在resolve状态时,得到一些值,那么为了获得这些值,我可以将其延迟输出就可得到。
var i
var promise = new Promise(function(resolve, reject) {
resolve("hello");
})
promise.then(data => {
i = data;
})
console.log(i);//undefined 因为方法还没执行完
setTimeout(()=>console.log(i),1000);//hello
3async的语义和用法
async函数返回的是一个Promise对象,如果用return的话,它会把这个值通过resolve封装成一个promise对象 ,相当于new Promise(resolve =>resolve(x))
async function testAsync() {
return "hello async";
}
const result = testAsync();
console.log(result);//Promise { 'hello async' }
而await就是等待一个async的返回值,可以是promise对象或者其他值,因此await后面实际是可以接普通函数调用或者直接量的。
function getSomething() {
return "something";
}
async function testAsync() {
return Promise.resolve("hello async");
}
async function test() {
const v1 = await getSomething();
const v2 = await testAsync();
console.log(v1, v2);
}
test();//something hello async
对await来说
如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
以下代码为不用async实现的一个异步功能,最主要是promise采用了组成链看上去是比较复杂的
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
//step1 with 300
//step2 with 500
//step3 with 700
//result is 900
//doIt: 1.530s
以下代码为使用async ,给我的感觉几乎就是同步代码,但实际上是异步的。因此可以很明显的感受到promise的参数传递太麻烦,因此我们引入了async/await
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();