js的同步和异步

首先我们需要复习一下 js 的同步和异步

JavaScript是一门单线程的语言,因此,JavaScript在同一个时间只能做一件事,单线程意味着,如果在同个时间有多个任务的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务,为了防止 ajax 这类任务执行等待时间过长,javascrpit 就设计了同步和异步任务

同步任务

同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务,当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务

异步任务

异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程,当我们打开网站时,像图片的加载,音乐的加载,其实就是一个异步任务


Es6的Promise异步规范

在 Es6 的 Promise 出来之前,我们一般都是使用回调函数来处理异步任务 ,在Promise规范提出后,异步编程就变的相对简单一些

Promise是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外)

Promise并未剥夺函数return的能力,因此无需层层传递callback,进行回调获取数据

Promise包括以下几个规范

  1. 一个promise可能有三种状态:等待pending 、已完成fulfilled 、已拒绝rejected
  2. 一个promise的状态只可能从 等待 转到 完成 态或者 拒绝 态,不能逆向转换,同时 完成 态和 拒绝 态不能相互转换
  3. promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致(即链式的then方法调用)
  4. then方法接受两个参数,(一般我们叫第一个回调参数为res、第二个为 err) ,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由 等待 态转换到 拒绝 态时调用,同时,then 可以接受另一个 promise 传入,也接受一个“类then”的对象或方法,即thenable对象

我们可以通过 if(typeof(Promise)==="function") 来测试浏览器是否支持promise

通过 new Promise 来进行 Promise 对象的创建

1
2
3
4
5
6
7
8
9
10
11
new Promise(
function (resolve, reject) {
// 一段耗时的异步操作
resolve('成功') // 数据处理完成
// reject('失败') // 数据处理出错
}
).then(
(res) => {console.log(res)}, // 成功
(err) => {console.log(err)} // 失败
)

Promise.then()的链式调用示例,then 被按照顺序组织起来,一个一个的执行,每个执行完后就会变成 完成 状态并进行下一个

前面也提到了,then方法其实是接受两个参数,虽然大部分时候我们都比较关注成功时的回调,但第二个参数失败的回调可以帮我们处理一些异常,除此之外,我们还可以使用catch来捕获错误,reject 的东西一定进入 then 的第二个回调,当没有写第二个回调的时候,会进入catch,如果没有then, 也可以直接进入catch。

err处理异常和catch都会返回一个promise实例,并且是resolved状态所以不会影响后续的then的执行,否则将会把后续的then的状态变为rejected 并且不会执行他们所登记的异步函数

then的链式调用 出错的then的链式调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
new Promise(resolve => {
setTimeout(() => {
resolve('hello')
}, 2000)
}).then(res => {
// 参数val = 'hello'
console.log(res)
return new Promise(resolve => {
setTimeout(() => {
resolve('world')
}, 2000)
})
}).then(res => {
// 参数val = 'world'
console.log(res)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
new Promise(resolve => {
setTimeout(() => {
resolve('hello')
}, 2000)
}).then(res => {
console.log(res+"这是第一个then")
return "第一个then return 了"
},err=>{
console.log("如果第一个出错,执行这个")
}).then(res => {
console.log(res)
// 假装第二个then输出第一个then的值后出错了
throw new Error("这是第二个then抛出的错误")
})
.then(res => {
return "第三个then"
},err=>{
console.log(err+"这是第三个then的第二个回调")
})
.then(res => {
//上一个then第二个回调处理完异常后,不影响这个的执行
console.log("第四个then执行了")
return "第四个then的return"
},err=>{
console.log(err+"这是第四个then的err")
})
.then(res => {
console.log(res)
console.log("第五个then执行了")
// 假装第五个then出错了
throw new Error("这是第五个then抛出的错误")
},err=>{
console.log(err+"这是第五个then的err")
})
.catch(err=>{
console.log("最后的catch执行了"+err)
})
.then(res => {
console.log("catch不影响下一个then的状态")
})

async 和 await

async 是作为函数的修饰符放在函数的前面 , 它的调用会返回一个 promise 对象,并且 Promise 还有 PromiseStatus 和 promiseValue,如果 async 函数本身返回值,当调用该函数成功时内部会调用 Promise.solve() 方法把它转化成一个 promise 对象作为返回,如果 函数内部抛出错误,就会调用 Promise.reject() 返回一个 promise 对象,想要获取到async 函数的执行结果,就要调用promise的 then 或 catch 来给它注册回调函数

总结一下,async 函数的执行会返回一个 promise 对象,并且把内部的值进行 promise 的封装。如果 promise 对象通过 then 或 catch 方法又注册了回调函数,async 函数执行完以后,注册的回调函数就会放到异步队列中,等待执行

await 关键字只能放到 async 函数里面,await 是等待的意思,等待直到Promise对象resolve,得到resolve的值作为await表达式的运算结果,我们一般在需要拿到请求数据后再操作的情况下使用。

执行下面的代码我们可以看到每等2秒会输出一个值,6秒后 三个数的和 和 第三个数几乎一起输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 2s 之后返回双倍的值
function doubleAfter2seconds(num) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2 * num)
}, 2000);
} )
}
async function testResult() {
let first = await doubleAfter2seconds(30);
console.log(first);
let second = await doubleAfter2seconds(50);
console.log(second);
let third = await doubleAfter2seconds(30);
console.log(third);
console.log(first + second + third);
}
testResult()