JS异步知识点总结

异步的概念
ajax 编程
异步实现的几种方式
回调函数
promise
生成器
async await
异步编程的概念
有异步编程,就有同步编程
console.log("Hello"); console.log("World"); for(let i=0;i<5;i++){ console.log(i); } console.log("F71");
通过上面的代码,我们可以看出同步代码的特点是由上至下依次执行。

异步代码,往往是非常消耗时间的代码,发送网络请求获取数据、读取文件。
console.log("Hello"); console.log("World"); // 发送网络请求,获取数据,然后打印出来 console.log("F71");
上面的代码,如果是以同步的方式来执行,那么“发送网络请求”这个操作就会阻塞后面的代码。

由于我们 js 又是单线程语言。所以当 js 遇到异步的问题的时候,处理的方式是优先处理同步代码,异步的代码扔给异步处理模块。异步处理模块处理完成之后,扔到任务队列里面,然后主线程再一个一个的从任务队列里面去获取异步操作的结果。

  • setTimeout(fn,time) 异步代码

  • setInterval(fn,time) 异步代码
    console.log("Hello"); console.log("World"); setTimeout(function(){ console.log("异步代码一") },3000); setTimeout(function(){ console.log("异步代码二") },1000); console.log("F71");
    Ajax
    ajax 技术一个实现步骤

  1. 创建 XMLHttpRequest 对象(创建一个跑腿的人)
  2. 由这个对象来和服务器进行通信(发送 HTTP 请求)
  3. 欢迎这个人回来,它把数据带回来了
  4. 通过 DOM 技术进行局部的刷新,将带回来的数据渲染在页面上面
mockjs

使用 mockjs 可以生成随机的数据,拦截 ajax 请求。
下面是一个 mockjs 的使用示例:

先来看一下如何生成随机的数据
// 如果是 id,可以不用获取 dom,直接通过该 id 就可以拿到 btn.onclick = function () { const data = Mock.mock({ // 随机生成一个包含 4 个对象的数组 'list|4': [{ // 对象的 id 属性名依次加 1 'id|+1': 1, // 随机产生一个中文的姓名 "name": '@cname()', // 随机生成一个地址 addr: '@county(true)', // 随机生成一个数字 大小在 18 到 30 'age|18-30': 1, // 随机生成一个日期 birth: '@date()', // 随机生成一个整数,0/1 ,根据这个来给“男”、“女” gender: '@integer(0, 1)', // 随机生成一个邮箱 email: '@EMAIL()', // 在数组中随机找一个 'like|1': ['看书', '运动', '听音乐'], // 1-100 中随机生成一个保留两位小数点 'score|1-100.2': 1, // 随机生成一个日期 time: "@date('yyyy-MM-dd')", // 用正则匹配 1 开头的 11 位数字的手机号 mobile: /^1[3-9]\d{9}$/ }] }) console.log(data); }
ajax 具体的代码实现

回调函数

在最早期的时候,要想拿到异步的结果,只能使用回调。

需求:每隔 2 秒打印一个数,从 1 开始到 3
setTimeout(function(){ console.log(1); },2000); setTimeout(function(){ console.log(2); },2000); setTimeout(function(){ console.log(3); },2000);
根据上面的需求,你很有可能就写出上面的代码,但是你会发现上面的代码并不能实现我们的需求。
究其原因,是因为三段代码都是异步代码,基本上是同时被扔到异步处理模块,然后基本上是同时一起在计时。基本上也是同一时间被扔到任务队列里面。
这里涉及到一个核心,就是我们要等待上一次的异步操作结束后,再开始下一次的操作。
setTimeout(function(){ console.log(1); setTimeout(function(){ console.log(2); setTimeout(function(){ console.log(3); },2000); },2000); },2000);
要想某一个操作在异步之后,只有通过回调的形式来实现,也就是说,将后一步操作,写在异步操作的回调函数中。

文件读取
const fs = require('fs'); // 引入 nodejs 中内置的 fs 模块 fs.readFile('./file1.txt',function(err1, data1){ console.log(data1.toString()); fs.readFile('./file2.txt',function(err2, data2){ console.log(data2.toString()); fs.readFile('./file3.txt',function(err3, data3){ console.log(data3.toString()); }) }) })

回调地狱问题(callback hell)

每隔两秒打印一个数,从 1~10
setTimeout(function(){ console.log(1); setTimeout(function(){ console.log(2); setTimeout(function(){ console.log(3); setTimeout(function(){ console.log(4); setTimeout(function(){ console.log(5); setTimeout(function(){ console.log(6); setTimeout(function(){ console.log(7); setTimeout(function(){ console.log(8); setTimeout(function(){ console.log(9); setTimeout(function(){ console.log(10); },2000); },2000); },2000); },2000); },2000); },2000); },2000); },2000); },2000); },2000);
通过上面的代码,我们已经看出问题来了,通过回调来解决异步的问题,有一个最大的问题,就是代码一直在往右边走,导致可读性很差。

Promise

Promise 是异步的一种解决方案,最早是由社区提出来一种异步解决方案,主要就是解决回调地狱的问题。

从 ES6 开始,Promise 被写入了官方标准。所以我们现在不需要任何的插件,就可以直接使用 Promise。

下面是一个 Promise 的简单示例:
const pm = new Promise(function(resolve,reject){ // resolve 和 reject 也是两个函数 // resolve 是在异步操作成功的时候调用 // reject 是在异步操作失败的时候调用 resolve('这是异步操作传递的数据'); }) pm.then((data)=>{ console.log(data); })
当异步操作完成的时候,我们直接 resolve,此时就会来到 then 方法,这个 then里面的函数就相当于以前嵌套的那个函数。
接下来我们来研究 promise 的一些语法细节。
`console.log('1');
const pm = new Promise(function(resolve,reject){
console.log("Hello"); // 同步
resolve('这是异步操作传递的数据'); // 异步代码
console.log("World"); // 同步
})
pm.then((data)=>{
console.log(data);
})
console.log('2');

// 1
// Hello
// World
// 2
// 这是异步操作传递的数据通过上面的代码,我们可以看出,promise 本身里面的代码是同步的,只不过在调用 resolve 的时候是异步代码。 一般来讲,我们会在 promise 里面放一个异步的操作,如果这个异步的操作成功,我们调用resolve,如果失败,我们调用 reject。const pm = new Promise(function(resolve,reject){
resolve();
})
pm.then(()=>{
console.log("成功的时候打印这句话")
},()=>{
console.log('失败的时候打印这句话');
})在上面的例子中,成功和失败都是写在 then 方法里面的。为了让成功和失败更加易读,推荐失败的时候使用 catch 来捕捉。const pm = new Promise(function(resolve,reject){
// resolve();
reject();
})
pm.then(()=>{
console.log("成功的时候打印这句话")
}).catch(()=>{
console.log("失败的时候打印这句话")
})`

通过 resolve 和 reject 是可以像 then 或者 catch 里面的方法传递数据的。
const pm = new Promise(function(resolve,reject){ resolve('xie'); // reject(); }) pm.then((data)=>{ console.log(data); // xie }).catch(()=>{ console.log("失败的时候打印这句话") })
使用 promise 改善之前回调地狱的问题。
new Promise((resolve,reject)=>{ setTimeout(()=>{ console.log(1); resolve(2); },2000) }).then((data)=>{ return new Promise((resolve,reject)=>{ setTimeout(function(){ console.log(data); resolve(3); },2000); }) }).then((data)=>{ setTimeout(function(){ console.log(data); },2000); })
文件读取的 promise 示例:
`const fs = require('fs');

new Promise((resolve,reject)=>{
fs.readFile('./file1.txt',(err,data)=>{
console.log(data.toString());
resolve();
})
}).then(()=>{
return new Promise((resolve,reject)=>{
fs.readFile('./file2.txt',(err,data)=>{
console.log(data.toString());
resolve();
})
})
}).then(()=>{
fs.readFile('./file3.txt',(err,data)=>{
console.log(data.toString());
})
})`

promise 相关的 api

Promise.all :用于多个 Promise 的实例,包装成一个新的 Promise 实例。
`let p1 = new Promise((resolve,reject)=>{
setTimeout(function(){
resolve(1);
},1000)
})

let p2 = new Promise((resolve,reject)=>{
setTimeout(function(){
resolve(2);
},5000)
})

let p3 = new Promise((resolve,reject)=>{
setTimeout(function(){
resolve(3);
},200)
})

Promise.all([p1,p2,p3]).then(res=>{
console.log(res);
})通过上面的代码示例,我们可以看出,异步的执行顺序会按照 all 方法里面指定的顺序来执行。 接下来再来看一个例子,读取文件的例子:const fs = require('fs');
let p1 = new Promise((resolve,reject)=>{
fs.readFile('./file1.txt',(err, data)=>{
resolve(data.toString());
})
})

let p2 = new Promise((resolve,reject)=>{
fs.readFile('./file2.txt',(err, data)=>{
resolve(data.toString());
})
})

let p3 = new Promise((resolve,reject)=>{
fs.readFile('./file3.txt',(err, data)=>{
resolve(data.toString());
})
})

Promise.all([p1,p2,p3]).then(res=>{
console.log(res);
})在 Promise.all 方法中,如果有一个为 reject,那么最终的结果就是 rejectconst fs = require('fs');
let p1 = new Promise((resolve,reject)=>{
fs.readFile('./file1.txt',(err, data)=>{
resolve(data.toString());
})
})

let p2 = new Promise((resolve,reject)=>{
fs.readFile('./file2.txt',(err, data)=>{
reject('失败');
})
})

let p3 = new Promise((resolve,reject)=>{
fs.readFile('./file3.txt',(err, data)=>{
resolve(data.toString());
})
})

Promise.all([p1,p2,p3]).then(res=>{
console.log(res);
}).catch((res)=>{
console.log(res);
})Promise.race : 和上面一样,同样是将多个 promise 封装成一个 promise,看哪个异步最先完成,得到的是最先完成的异步结果let p1 = new Promise((resolve,reject)=>{
setTimeout(function(){
resolve(1);
},1000)
})

let p2 = new Promise((resolve,reject)=>{
setTimeout(function(){
resolve(2);
},5000)
})

let p3 = new Promise((resolve,reject)=>{
setTimeout(function(){
resolve(3);
},200)
})

Promise.race([p1,p2,p3]).then(res=>{
console.log(res);
})

// 打印输出 3,但是时间仍然按照最长的来计算Promise.resolve : 可以将现有的对象转换为 Promise 对象Promise.resolve('666');
// 等价于
new Promise(resolve=>resolve('666'));`

宏任务和微任务

如果在主线程中遇到异步任务,会被扔到异步处理模块,异步处理模块处理完之后,会被放入到任务队列。

但是在任务队列中,我们的任务其实分成了宏任务和微任务。

执行的顺序是:首先将所有的微任务执行一遍,然后再取出一个宏任务执行。又去检查微任务队列,看微任务队列有没有新的微任务,如果有,将微任务队列里面的所有微任务又全部执行完,之后又去执行一个宏任务....
宏任务:

  • I/O
  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame

微任务:

  • process.nextTick
  • Promise.then catch finally
    案例:
    `console.log('1');

setTimeout(function(){
console.log('2');
process.nextTick(function(){
console.log('3');
})
new Promise(function(resolve){
console.log('4');
resolve();
}).then(function(){
console.log('5')
})
})

process.nextTick(function(){
console.log('6');
})

new Promise(function(resolve){
console.log('7');
resolve();
}).then(function(){
console.log('8');
})`
解析:1、7、6、8、2、4、3、5

首先应该打印1,因为这是同步代码,接下来就是 setTimeout,整个是一个 宏任务

接下来的 process.nextTick 也是一个异步任务,而且是一个微任务

接下来来到下面的 promise,promise 的第一个打印语句是一个同步代码,所以直接输出

promise 中的 resolve 对应的 then是一个异步任务,并且是一个微任务

宏任务队列:setTimout

微任务队列:process.nextTick、then

接下来,就应该去任务队列挨着挨着执行。首先执行所有的微任务,所以process.nextTick里面的打印语句会被执行。

再接着执行微任务中的 then,打印输出 8
微任务队列已经执行完毕,所以应该去执行宏任务。
宏任务队列:
微任务队列:process.nextTick(3)、then(5)
最后就依次打印输出3、5

生成器

各种遍历

数组的遍历,代码如下:
`const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 接下来,我们来遍历这个数据
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}

arr.forEach((item) => {
console.log(item);
})

for (let i in arr) {
console.log(arr[i]);
}

for (let i of arr) {
console.log(i);
} // 遍历集合
const list = new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
for(let ele of list){
console.log(ele);
}

let obj = {
name : 'xiejie',
age : 18,
gender : 'male',
intro : function(){
console.log('Hello~');
}
}
for(let attr in obj){
console.log(attr);
}`
对数组、集合、对象进行了遍历,所谓遍历,就是取出它的每一项。

之所以我们可以对数组、集合进行遍历,是因为数组、集合里面内置了迭代器。

换句话说,如果一个东西没有迭代器,那么你是不能够对它进行遍历的。

也就是说,不能通过 for-in、for-of 来进行遍历。
获取一个数组的迭代器,代码如下:
`const arr = [1, 2, 3];

const it = arrSymbol.iterator; // 获取数据的迭代器存储到 it 里面
// 迭代器是一个对象,每一次调用 next 方法,就可以获取下一个元素
// 所以当我们使用 for-of 来遍历数组的时候,实际上它的内部是在调用迭代器的 next 方法
console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: 2, done: false }
console.log(it.next()); // { value: 3, done: false }
console.log(it.next()); // { value: undefined, done: true }`

证明一个东西要有迭代器才能够遍历出来:
let obj= { name : 'xie', age : 18, gender : 'male', intro : function(){ console.log('Hello~'); }, [Symbol.iterator]: function(){ let i = 0; let keys = Object.values(this); return { next : function(){ return { value : keys[i++], done : i > keys.length } } } } } for(let i of obj){ console.log(i) } // xie // 18 // male // [Function: intro]
生成器就是专门用来生成迭代器的。
function* say(){ yield "开始"; yield "执行"; yield "结束"; } const it = say(); // 生成一个迭代器 console.log(it.next()); // { value: '开始', done: false } console.log(it.next()); // { value: '执行', done: false } console.log(it.next()); // { value: '结束', done: false } console.log(it.next()); // { value: undefined, done: true }

let obj= { name : 'xiejie', age : 18, gender : 'male', intro : function(){ console.log('Hello~'); } } // 接下来我们使用生成器的方式来生成一个迭代器 function* say(){ for(let i in obj){ yield obj[i]; } } const it = say(); console.log(it.next()); console.log(it.next()); console.log(it.next()); console.log(it.next());
生成器是可以拿来做异步操作。
使用生成器改写异步每隔 2 秒打印一个数的示例:
`function test(n){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log(n);
resolve()
},2000);
})
}

// 接下来书写一个生成器,生成器里面就封装了一系列的异步操作
function* say(){
yield test(1);
yield test(2);
yield test(3);
yield test(4);
yield test(5);
}

// 接下来需要书写一个执行器
const it = say(); // next 方法 返回 {value : Promise, done : flase}
it.next().value.then(()=>{
return it.next().value;
}).then(()=>{
return it.next().value;
}).then(()=>{
return it.next().value;
}).then(()=>{
return it.next().value;
})
async、awaitfunction timer(){
return new Promise((resolve)=>{
setTimeout(()=>{
console.log("Hello");
resolve();
},2000);
})
}

// 一旦我的函数前面添加了 async 关键字
// 那么就说明这是一个异步函数,说明我可以使用 await 关键字来等待上一次的异步结果
async function getTimer(){
await timer();
console.log("World");
}
getTimer();每隔 2 秒打印一个数:function timer(num){
return new Promise((resolve)=>{
setTimeout(()=>{
console.log(num);
resolve();
},2000);
})
}

// 一旦我的函数前面添加了 async 关键字
// 那么就说明这是一个异步函数,说明我可以使用 await 关键字来等待上一次的异步结果
async function getTimer(){
await timer(1);
await timer(2);
await timer(3);

}
getTimer();`

posted on 2020-12-26 18:17  wpwp123  阅读(106)  评论(0)    收藏  举报