QX-AI
GPT-4
QX-AI初始化中...
暂无预设简介,请点击下方AI实时简介按钮。
介绍自己
生成本文简介
推荐相关文章
AI实时简介

同步与异步

JS是单线程语言,不能多个线程并发执行,即同一时间,只能处理一个任务

单线程指的是浏览器中只有一个主线程(JS引擎线程)负责解释和执行JS代码,但是浏览器还提供了额外多个副线程用于实现异步非阻塞
1、事件触发线程
2、定时触发器线程
3、异步http请求线程
4、GUI渲染线程

副线程处理完成之后会把对应的回调函数交给消息队列(任务队列)维护,JS引擎线程会在同步任务执行完后,去消息队列中每次取一个任务执行

两种任务
同步任务:在主线程上排队执行任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务:不进入主线程,而是进入消息队列的任务

宏、微任务

每次执行栈中执行的代码就是一个宏任务,在执行宏任务时遇到Promise等,会创建微任务(.then()里面的回调),并加入到微任务队列队尾。

微任务必然是在某个宏任务执行过程时创建的

宏任务来源:script,setTimeout,setInterval,setImmediate,I/O,requestAnimationFrame,UI rendering
微任务来源:process.nextTick,promisesObject.observe,MutationObserver

微任务的执行:在执行栈为空时,会从消息队列中取一个回调函数作为宏任务,在这个宏任务执行前,会执行微任务队列中所有的微任务

事件循环

事件循环就是主线程重复从消息队列中取消息(回调函数)并执行的过程

参考:
一篇搞定(Js异步、事件循环与消息队列、微任务与宏任务)
JS的事件轮询(Event Loop)机制

Promise

Promise(承诺) ES6的新类,是异步编程的新解决方案

作用:把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数

回调地狱:通过回调函数依次执行多个异步操作。Promise通过链式操作解决了回调地狱问题,也能更好地进行错误处理

1
2
3
4
5
6
7
8
9
10
setTimeout(function () {
console.log("等待一秒");
setTimeout(function () {
console.log("再等一秒");
setTimeout(function () {
console.log("又等一秒");
}, 1000);
}, 1000);
}, 1000);

以fetch为例,它返回一个Promise对象,承诺会在某个时刻返回请求到的数据,请求成功,会调用.then中的回调函数,失败则调用.catch中的

任意一个then出现错误都会触发catch,而then不再往下执行,但catch后的then会继续执行

.then中也可以返回一个Promise,如 res.json() 返回一个Promise,承诺之后将请求的数据返回为json格式,以此实现链式操作

无论是成功还是失败,最后都会执行 .finally 中的回调函数,可以用于提示请求完成、关闭加载动画等

1
2
3
4
5
fetch('https://api.ooomn.com/api/yan?encode=json')
.then(res=> res.json() )
.then(data=> console.log(data) )
.catch(err => console.error(err) )
.finally(()=> console.log('请求结束') )

使用

Promise本身是一个构造函数,其对象用来封装一个异步操作,并可以获取其成功或失败的结果值

它拥有all、reject、resolve等方法,原型上有then、catch等方法

有三种状态:pending(进行中)、resolved / fulfilled(已成功)和rejected(已失败),初始状态pending,只能转变为其它状态一次
状态[[PromiseState]]是Promise实例对象的属性,但并不是一个暴露的属性,无法获取和外部修改

Promise的构造函数接收一个函数参数,传入两个参数:resolvereject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。
resolve() 会将Promise的状态置为resolved,reject() 会将Promise的状态置为rejected

.then(onResolved, onRejected) onResolved 和 onRejected 是两个回调函数,分别接收 resolve() 和 reject() 传入的参数,并在对应状态时回调

注意:Promise中所谓的成功或失败,需要根据业务的需要而定义,它只是提供了这两个状态供使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const p = new Promise((resolve, reject)=>{
// 随机生成0或1
let num = Math.round(Math.random());
// 定义num为1时是成功,0为失败,调用resolve()
if(num){
resolve(num);
}else{
reject(num);
}
});

p.then(
data => { console.log(data) },
data => { console.log(data) }
);

Promise封装AJAX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function sendAJAX(url) {
// 返回一个Promise对象
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open('GET', url, true);
xhr.send();
// 处理结果
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
// 请求完成进行判断
if ((xhr.status >= 200 && xhr.status <= 300) || xhr.status === 304) {
// 定义为请求成功,将响应传入
resolve(xhr.response);
} else {
// 定义失败将状态码传入
reject(xhr.status);
}
}
};
})
}

使用封装好的 sendAJAX()

1
2
3
4
5
6
7
sendAJAX('https://api.ooomn.com/api/yan?encode=json')
.then(
res => console.log(res),
reason => console.log(reason)
)
.catch( err => console.log(err) )
.finally(()=> console.log('请求完成') )

基本流程

属性和方法

1、[[PromiseResult]] 承诺结果,保存着异步任务成功或失败的结果,即保存着 resolve() 或 reject() 传入的值,then中回调函数的参数可获取

2、Promise构造函数传入的函数参数,其内部的操作是同步

1
2
3
4
5
6
new Promise((resolve, reject) => {
console.log('1');
})
console.log('2');
// 1
// 2

3、then 和 catch
Promise.prototype.then : (onResolved, onRejected) => {}
(1) onResolved 函数: 成功的回调函数 (value) => {}
(2) onRejected 函数: 失败的回调函数 (reason) => {}
说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调
返回一个新的 promise 对象

Promise.prototype.catch : (onRejected) => {}
(1) onRejected 函数: 失败的回调函数 (reason) => {}
说明: then()的语法糖, 相当于: then(undefined, onRejected)

通常不使用then的第二个参数,而是直接使用catch捕获失败

4、resolve 和 reject
Promise.resolve : (value) => {}
(1) value: 成功的数据或 promise 对象
说明: 当传入promise对象时,参数的结果决定了promise的结果。将状态从 pending 转为 fulfilled
返回一个成功/失败的 promise 对象

Promise.reject : (reason) => {}
(1) reason: 失败的原因
将状态从 pending 转为 rejected
永远返回一个失败的 promise 对象

1
2
3
4
5
6
7
8
9
10
let p1 = Promise.resolve(123);
p1.then(data => console.log(data));// 123

let p2 = Promise.resolve(new Promise((resolve, reject) => {
reject('Error');
}));
p2.catch(reason => {
console.log(reason);// Error
})

5、all
Promise.all : (promises) => {}
(1) promises: 包含 n 个 promise 对象的数组
返回一个新的 promise, 所有的 promise 都成功才成功, 只要一个失败就直接失败,多个失败返回第一个失败

1
2
3
4
5
6
7
8
9
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
const result = Promise.all([p1, p2, p3])
result.then(data=>console.log(data))
// (3) ['OK', 'Success', 'Oh Yeah']

6、race
Promise.race : (promises) => {}
(1) promises: 包含 n 个 promise 的数组
返回一个新的 promise, 第一个改变状态的 promise 的结果就是返回的结果状态

1
2
3
4
5
6
7
8
9
10
11
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
const result = Promise.race([p1, p2, p3]);
result.then(data => console.log(data));
// Success

更多

1、then的返回值
promise.then()返回一个新 promise 对象
其结果状态如下:
(1) then中抛出异常,返回的 promise 状态为 rejected, reason 为抛出的异常
(2) 返回的是非 promise 的任意值, 状态为 resolved, value 为返回的值
(3) 返回的是另一个 promise, 该 promise 的结果就是返回的 promise 的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const obj = { name: 'chuckle' }
const p = new Promise((resolve, reject) => {
resolve(obj);
})
p.then(data => {
console.log(data);// {name: 'chuckle'}
data.age = 19;
return Promise.resolve(JSON.stringify(data));
}).then(data => {
console.log(data);// {"name":"chuckle","age":19}
}).then(data => {
console.log(data);// undefined
})

2、异常穿透
多个then链式调用,只需要在最后写一个catch,当一个then失败,就不再执行后续的then,直接到catch,catch后面的then会正常执行

1
2
3
4
5
6
7
8
9
10
11
12
13
const p = Promise.reject('err');
p.then(data => {
console.log(1);
}).then(data => {
console.log(2);
}).catch(err => {
console.log('err')
}).then(data => {
console.log(3);
})
// err
// 3

3、中断Promise链
在回调函数中返回一个 pendding 状态的 promise 对象,因为then中回调函数是在 Promise 状态改变时调用的

1
2
3
4
5
6
7
8
9
10
11
12
const p = Promise.resolve('err');
p.then(data => {
console.log(1);
}).then(data => {
console.log(2);
return new Promise(()=>{})
}).then(data => {
console.log(3);
})
// 1
// 2

手写Promise

咕咕咕咕咕咕~~

async-await

使用Promise对象去实现异步操作,通常会出现一堆异步任务到点了该处理的情况,每次执行的顺序都不一定一样
async-await允许我们用同步编程的方式去写异步代码,async是基于Promise的语法糖

async函数永远返回一个Promise对象,和then的规则是一样的

await 右侧的表达式一般为 promise 对象, 但也可以是其它的值
1、如果表达式是 promise 对象, await 返回的是 promise 成功的值
2、如果表达式是其它值, 直接将此值作为 await 的返回值

声明一个async函数
1
2
3
4
5
6
7
8
9
10
11
12
13
async function fun(){
console.log('chuckle')
return 'qx'// 返回什么,返回的Promise对象的结果就是什么
}
console.log(fun())
//Promise {<fulfilled>: 'qx'}
// [[Prototype]]: Promise
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: "qx"
fun().then(data=>{
console.log(data);// qx
})

async函数中的 await 能让js引擎等待其后的代码(通常是返回Promise对象的函数)有结果后再往下执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function a(){
return new Promise((resolve, reject) =>{
setTimeout(() =>{
resolve(2)
}, 1000);
})
}
async function fun(){
console.log(1)
let num = await a();
console.log(num)
console.log(3)
}
fun();
// 1
// 等待一秒后
// 2
// 3

当多个fetch运行时,结果输出顺序有时并不是代码顺序,有时我们需要它按顺序执行,如果嵌套到then中,反而不美观

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fetch('https://api.ooomn.com/api/yan?encode=json')
.then(res=> res.json() )
.then(data=> {
console.log(data)
console.log(1)
})

fetch('https://api.ooomn.com/api/yan?encode=json')
.then(res=> res.json() )
.then(data=> {
console.log(data)
console.log(2)
})

fetch('https://api.ooomn.com/api/yan?encode=json')
.then(res=> res.json() )
.then(data=> {
console.log(data)
console.log(3)
})

使用 async 优化fetch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function fun(){
const url = 'https://api.ooomn.com/api/yan?encode=json';
try{
let res = await Promise.all(
[fetch(url), fetch(url), fetch(url)]
);
let jsons = res.map(res => res.json());
// json()返回的是promise对象,还需要再进行一次Promise.all
let values = await Promise.all(jsons)
console.log(values);
} catch(err){
console.log(err);
}
}
fun();

让一个函数等待另一个函数中fetch执行完

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async function fun(){
const url = 'https://api.ooomn.com/api/yan?encode=json';
try{
let res = await Promise.all(
[fetch(url), fetch(url), fetch(url)]
);
let jsons = res.map(res => res.json());
// json()返回的是promise对象,还需要再进行一次Promise.all
let values = await Promise.all(jsons)
return values;
} catch(err){
console.log(err);
}
}
async function fn(){
let data = 1;
data = await fun();
console.log(data);
}
fn();// (3) [{…}, {…}, {…}]

async-await等待AJAX请求完成,不使用.then

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
// 之前封装的Promise形式AJAX
function sendAJAX(url) {
// 返回一个Promise对象
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open('GET', url, true);
xhr.send();
// 处理结果
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
// 请求完成进行判断
if ((xhr.status >= 200 && xhr.status <= 300) || xhr.status === 304) {
// 定义为请求成功,将响应传入
resolve(xhr.response);
} else {
// 定义失败将状态码传入
reject(xhr.status);
}
}
};
})
}

// 点击按钮后等待请求完成再显示请求内容
btn.addEventListener('click',async function(){
let json = await sendAJAX('https://api.ooomn.com/api/yan?encode=json');
// 后面进行操作
console.log(json);
});

promisify

Javascript异步演化史,是从callback到Promise再到Async/Await的历程

对于一些老函数,可以使用 nodejs-util、其它第三方库中的 promisify 方法将其转为 Promise 形式的函数

promisify函数作用就是把 callback 形式转成 promise 形式。

只有符合 nodeCallback 的函数才能被 promisify 转化
nodeCallback 有两个条件:1. 回调函数在主函数中的参数位置必须是最后一个;2. 回调函数参数中的第一个参数必须是 error

nodejs中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const fs = require('fs');
const util = require('util');
const mineReadFile = util.promisify(fs.readFile);

async function main(){
try{
let data1 = await mineReadFile('./1.txt');
let data2 = await mineReadFile('./2.txt');
let data3 = await mineReadFile('./3.txt');
console.log(data1 + data2 + data3);
}catch(e){
console.log(e);
}
}
main();

简单实现一个promisify

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
var promisify = (func, ctx) => {
// 返回一个新的function
return function() {
// 初始化this作用域
var ctx = ctx || this;
// 新方法返回的promise
return new Promise((resolve, reject) => {
// 调用原来的非promise方法func,绑定作用域,传参,以及callback(callback为func的最后一个参数)
func.call(ctx, ...arguments, function() {
// 将回调函数中的的第一个参数error单独取出
var args = Array.prototype.map.call(arguments, item => item);
var err = args.shift();
// 判断是否有error
if (err) {
reject(err)
} else {
// 没有error则将后续参数resolve出来
args = args.length > 1 ? args : args[0];
resolve(args);
}
});
})
};
};

// nodeCallback方法func1
var func1 = function(a, b, c, callback) {
callback(null, a+b+c);
}
// promise化后的func2
var func2 = promisify(func1);
// 调用后输出6
func1(1, 2, 3, (err, reuslt) => {
if (!err) {
console.log(result); //输出6
}
})
func2(1, 2, 3).then(console.log); //输出6