JS高级--异步编程

异步编程

有异步编程,就有同步编程。以前我们所写的代码就都是同步代码。

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"); //Hello World  F71 异步代码二  异步代码一

  

Ajax

什么是 ajax ?

ajax 是在 2004 年左右提出的一项技术,但是这并不是一门全新的技术,而是早就存在的好几项技术的整合。用到了 DOM 编程、CSS、XHR 的对象。ajax 翻译成中文叫做“异步无刷新”技术,主要就是用来和服务器端进行通信。ajax 技术出来之后,大大提升了用户体验。因为在 ajax 中,是由 XHR 对象来发送请求,用户还可以继续操作现有的网页。ajax 技术现在基本上是无处不在。例如谷歌地图、搜索词汇联想、注册的时候验证用户名是否被占用,这些都用到了 ajax 技术。 

ajax 技术一个实现步骤
  1. 创建 XMLHttpRequest 对象(创建一个跑腿的人)

  2. 由这个对象来和服务器进行通信(发送 HTTP 请求)

  3. 欢迎这个人回来,它把数据带回来了

  4. 通过 DOM 技术进行局部的刷新,将带回来的数据渲染在页面上面

mockjs

使用 mockjs 可以生成随机的数据,拦截 ajax 请求。

下面是一个 mockjs 的使用示例:

<body>
    <button id="btn">生成随机数据</button>
    <script src="https://cdn.bootcdn.net/ajax/libs/Mock.js/1.0.1-beta3/mock-min.js"></script>
    <script>
        // 如果是 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);
        }
    </script>
</body>

  

ajax 具体的代码实现

具体示例看下面的代码:

<body>
    <button id="btn">发送 ajax 请求</button>
    <script src="https://cdn.bootcdn.net/ajax/libs/Mock.js/1.0.1-beta3/mock-min.js"></script>
    <script>
        // 使用 Mock 来拦截上面的 ajax 请求
        Mock.mock(/getStu/, function () {
            return 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}$/
                }]
            })
        })
        btn.onclick = function () {

            // 1. 创建 XMLHttpRequest 对象(创建一个跑腿的人)

            let xhr = null;
            // 解决兼容性问题
            if (window.XMLHttpRequest) {
                xhr = new XMLHttpRequest();
            } else {
                xhr = new ActiveXObject("Microsoft.XMLHTTP");
            }
            console.log(xhr);
            // 2. 由这个对象来和服务器进行通信(发送 HTTP 请求)
            // GET 和 POST 的区别
            // 如果是 GET 请求,往服务器带过去的数据是拼接在 URL 后面的
            // 如果是 POST 请求,往服务器带过去的数据放在请求体

            xhr.open('GET', '/getStu/', true); // 打开一个链接
            xhr.send(null); // 正式的发送请求

            // xhr.open('POST','/login',true);
            // // 设置 HTTP 的请求头
            // xhr.setRequestHeader("Content-type","application/x-www-form-urlencodeed;charset=UTF-8");
            // xhr.send("name='xiejie'&age=18");

            // 3. 欢迎这个人回来,它把数据带回来了
            // xhr 去服务器取数据,去的过程是有一个状态记录的

            // 请求未初始化(0):即还没有调用 send() 方法;
            // 服务器连接已建立(1):即已调用 send() 方法,正在发送请求;
            // 请求已接收(2):即 send() 方法执行完成;
            // 请求处理中(3):即正在解析响应内容;
            // 请求已完成(4):且响应已就绪,即响应内容解析完成,可以在客户端进行调用了;
            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    // 4. 通过 DOM 技术进行局部的刷新,将带回来的数据渲染在页面上面
                    console.log(xhr.responseText);
                }
            }


        }
    </script>
</body>

  异步实现的形式有哪些:

- 回调
- promise
- 生成器
- async await

回调函数

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

需求:每隔 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);  

所以在早期的 js 当中,要想某一个操作在异步之后,只有通过回调的形式来实现,也就是说,将后一步操作,写在异步操作的回调函数中。

回调地狱问题(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("失败的时候打印这句话")
})

  每隔两秒打印一个数:

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 相关的 api

Promise.all :用于多个 Promise 的实例,包装成一个新的 Promise 实例,异步的执行顺序会按照 all 方法里面指定的顺序来执行,,如果有一个为 reject,那么最终的结果就是 reject。

Promise.race : 和上面一样,同样是将多个 promise 封装成一个 promise,看哪个异步最先完成,得到的是最先完成的异步结果。

Promise.resolve : 可以将现有的对象转换为 Promise 对象

Promise.resolve('Hello');
// 等价于
new Promise(resolve=>resolve('Hello'));  
宏任务和微任务

在主线程中遇到异步任务,会被扔到异步处理模块,异步处理模块处理完之后,会被放入到任务队列。但是在任务队列中,我们的任务其实分成了宏任务和微任务。

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

宏任务:

  • 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

  

生成器

各种遍历

首先我们来看一下数组的遍历,代码如下:

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);
}

  

在上面的代码中,我们对数组、集合、对象进行了遍历,所谓遍历,就是取出它的每一项。之所以我们可以对数组、集合进行遍历,是因为数组、集合里面内置了迭代器。

获取一个数组的迭代器,代码如下:

const arr = [1, 2, 3];

const it = arr[Symbol.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 }

  生成器就是专门用来生成迭代器的

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 }

 每隔两秒打印一个数:

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;
})

  因为书写执行器非常的麻烦,所以出现了一个叫做 co 的模块,可以自动的执行生成器。

// 接下来我们使用生成器来改写读取文件的操作
const fs = require('fs');
const co = require('co');

let arr = [];
function test(fileName){
    return new Promise((resolve,reject)=>{
        fs.readFile(fileName,(err,data)=>{
            arr.push(data.toString());
            resolve();
        });
    })
}

function* say(){
    yield test('./file1.txt');
    yield test('./file2.txt');
    yield test('./file3.txt');
    console.log(arr);
}

co(say);

  异步终结解决方案 async、await

function 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 @ 2020-12-22 22:45  瓜豆のO泡  阅读(117)  评论(0)    收藏  举报