0%

javaScript异步操作学习


javascript的单线程理解:

  1. 首先来说javascript是基于浏览器的,也被叫做浏览器语言,而浏览器是多线程内核的,一般有以下常驻线程:
  • 渲染引擎线程:该线程负责渲染浏览器页面
  • JS引擎线程:该线程负责JS的解析和执行
  • 定时触发器线程:处理定时事件,比如setTimeout,setInterval
  • 事件触发线程:处理DOM事件
  • 异步http请求线程:处理http请求
  • 注意:渲染线程和JS引擎线程不能同时进行
  1. JS引擎线程负责JS,负责JS代码的解析和执行,而Javascript之所以是单线程是因为浏览器在运行的时候之开启了一个JS引擎线程来解析和执行JS。

  2. 所以我们应该知道的是因为为了避免操作DOM时产生混乱,浏览器只会开启一个JS引擎线程,而与I/O操作、定时器的计时和事件监听…等都是由浏览器提供的其他线程完成的。

同步Synchronous与异步Asynchronous

引用一个例子理解同步和异步的关系:在公路上,汽车一辆接一辆,有条不紊的在a:一条主道路上单向运行。这时,有b:一辆车坏掉了。假如它停在原地进行修理,那么后面的车就会被堵住没法行驶,交通就乱套了。幸好旁边有c:应急车道,可以把d:故障车辆推到应急车道e:修理,而正常的车流不会受到任何影响。等车修好了,再f:从应急车道回到正常车道即可。唯一的影响就是,应急车道用多了,原来的车辆之间的顺序会有点乱

理解:

  •   a:js引擎的主线程; 
    
  •   b:主线程当中执行的一个同步任务; 
    
  •   c:任务队列(不在主线程); 
    
  •   d:一个异步任务; 
    
  •   e:将一个异步任务放入消息队列;
    
  •   f:异步任务需等待主线程栈中所有任务执行完后,由事件循环机制将一个异步任务放进主线程
    

任务队列和事件循环

  • 任务队列(消息队列) 是一个先进先出的队列,它里面存放着各种消息–>回调函数可以是一个消息

  • 事件循环 是指主线程重复从消息队列中取出消息然后执行的循环执行机制,即基于Event LOOP:

    1
    2
    什么是Event LOOP:
    https://www.ruanyifeng.com/blog/2013/10/event_loop.html

理解一些异步操作(异步编程)

我们已经知道javascript是单线程执行任务,即后一个任务必须等待前一个任务的完成,但是我们必须这样吗?考虑到效率就需要一些异步操作,即可以实现将一些耗时的任务比如等待文件的读取挂起来(进入任务队列),再进行下一个任务的执行,知道文件的读取有了结果或者响应后,再回过头执行任务队列的任务。

有以下几种方式可以实现异步编程:

  • 回调函数
  • 事件监听
  • 发布/订阅
  • promise
  • generator(ES6)
  • async/await(ES7)

回调函数

回调函数是实现异步最简单的操作和最基本的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.异步操作前:
function fn1 () {
console.log('Function 1')
}

function fn2 () {
setTimeout(() => {
console.log('Function 2')
}, 500)
}

function fn3 () {
console.log('Function 3')
}

fn1();
fn2();
fn3();

上面的代码意图是顺序执行fn1、fn2和fn3函数

但是如果fn2可以视作一个延迟了500毫秒执行的异步函数,也就是说fn2的函数会被挂起而进入任务队列等待后面fn3函数执行完毕后fn2再执行

所以我们想要保持原来的意图,即fn1执行后然后执行fn2再执行fn3的这个顺序,于是我们这儿可以使用回调函数解决:

1
2
3
4
5
6
7
8
9
10
11
//使用回调函数改变代码结构
function fn2 (f) {
setTimeout(() => {
console.log('Function 2')
f()
}, 500)
}

fn1();
fn2(fn3);
//上面这段代码是回调函数的基本用法:将一个任务的函数名作为参数传入另一个任务,这个作为作为参数的函数名的函数就是回调函数

这样就达到了最初的意图-fn1,fn2,fn3依次执行

但是fn2和fn3完全耦合在一起,如果有多个类似的函数,很有可能出现fn1(fn2(fn3(fn4(…))))这样的情况,这就是回调地狱(多个回调函数嵌套的情况)。

Promise

  • Promise 对象是 JavaScript 的异步操作解决方案,为异步操作提供统一接口,也就是为解决回调函数噩梦而提出的写法–>将回调函数的横向加载变成纵向加载。

  • 首先来说Promise是一个对象,也是一个构造函数

  • 理解Promise机制:Promise是一个代理对象,这个值可以称为Promise对象的状态,而Promise对象就是通过自身的状态来控制异步函数–>Promise实例具有三种状态(PromiseStatus):

    • 异步操作未完成(pending): promise暂时还没有被解决也没有被拒绝,仍然处于pending状态
    • 异步操作成功(fulfilled):promise已经被resolved没有发生错误
    • 异步操作失败(rejected):promise已经被rejected,出现错误

我们可以通过一个new一个Promise:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//通常我们是通过new一个Primise对象,然后传入一个函数(这里是匿名函数)
//(其中入参resolve和reject都是改变状态的函数,由javascript引擎实现)
var promise = new Promise(function(resolve,reject){//刚定义时状态默认为pending

//需要异步执行的代码...

if (/* 异步操作成功 */){
resolve(value);
} else { /* 异步操作失败 */
reject(new Error());
}
});

//注意四点:
//1.执行resolve函数会将状态改为fulfilled:
//resolve(retValue):retValue可以是任何值,null也可以,它会传递给后面的then方法里面的function去使用

//2.执行reject函数会将状态改为rejected:
//通过rejected(err)传入的err理论上也是没有限制类型的,但是一般会传入一个错误

//3.状态改变是持久的,可以查询到,并且状态变化后可以影响后续的then的行为


对于一个promise, 我们可以使用它上面的三个方法:

  1. ·then(): 在一个promise被resolved后调用,内部有两个方法:

    1
    2
    3
    4
    5
    6
    promise.then(function onFulfilled(value){ console.log(value);//return undefined },
    function onRejected(error){ console.error(error);//return undefined})

    //我们在使用.then多次调用形成一个调用链时,比如:promise.then(step2).then(step3)....then(stepn)。每个then返回的都必须是一个promise对象,假如then方法里面的onFulfilled函数返回的不是promise,比如是Number或string,那么架构会用Promise.resolve(return的返回值)包装成一个resolved状态的promise返回

    //一般只会使用resolve,也就是只在传入一个then中传入一个匿名函数
  2. .catch(): 在一个promise被rejected后被调用

  3. .finally(): 不论promise是被resolved还是reject总是调用

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//1.以加载图片压缩图片、为图片应用过滤器并保存它的例子说明:

//定义函数
function getImage(file) {
return new Promise((res, rej) => {
try {
const data = readFile(file)
res(data) // 如果图片加载完成且一切正常,用加载完的图片解决(resolve)promise
} catch(err) {
rej(new Error(err)) // 果在加载文件时某个地方有一个错误,我们将会用发生的错误拒绝(reject)promise
}
})

//调用:
getImage(file)
.then(image => console.log(image))
.then(compressedImage => applyFilter(compressedImage)) // 添加滤镜
.then(filteredImage => saveImage(filteredImage))// 保存图片
.catch(error => console.log(error))//
.finally(() => console.log("All done!"))

//2.注意:
//通常在.then步骤结束后添s加一个catch()函数依次捕捉每一个then方法可能出现的rejectd返回值

“微任务”:

Promise 的回调函数属于异步任务,准确来说是不正常的异步任务(微任务),执行顺序在同步任务和正常异步任务之间:

1
2
3
4
5
6
7
8
9
10
11
12
setTimeout(function() {
console.log(1);
}, 0);

new Promise(function (resolve, reject) {
resolve(2);
}).then(console.log);

console.log(3);
// 3
// 2
// 1

Async/Await

参考:

异步操作概述:
https://wangdoc.com/javascript/async/general.html

js中的异步与同步:
https://www.cnblogs.com/Yellow-ice/p/10433423.html

JavaScript异步机制详解:
https://blog.csdn.net/LRH0211/article/details/79172822

JavaScript Promise 和Async/Await一看就懂:
https://blog.csdn.net/qq_36174666/article/details/106353457

js异步操作、事件监听、发布-订阅模式、Generator函数:
https://zhuanlan.zhihu.com/p/67629425

浅析js中的Promise和async/await:
https://blog.csdn.net/yes1983/article/details/83629647

坚持原创技术分享,您的支持将鼓励我继续创作.