一、异步:现在与将来
1 - 异步机制
- 什么是异步机制
a. 对于一段js代码,主要分为两块,一块是现在执行,一块是将来执行。
b. 一旦把一部分代码包装成一个函数,并指定它在响应某个事件时执行,那就形成了一个将来时代码块,同时也引入了异步机制。
2 - 事件循环机制
- 什么是事件循环机制
a. js 引擎本身做的事情是:在需要的时候,在给定的任意时间段执行单个代码块。即 js 引擎本身并没有时间概念,只是按需执行js的代码片段。
b. js 引擎运行的宿主环境都提供了一种机制:能够处理程序中多个块的执行,并且在每个块执行时都调用js 引擎。这种机制被称为 事件循环机制。
- 关于setTimeout的异步说明
a. 在执行到 setTimeout() 时,并没有立刻把回调函数挂在事件循环队列中,它只是设定了一个定时器。当定时器结束后,宿主环境会把回调函数放在事件循环队列中。
b. 如果计时器结束后,事件循环队列中有20个项目在等待,那回调函数就被放在20个项目之后,当前面20个项目执行结束后,才会触发回调函数。即:定时器只能保证事件不会在设定的时间之前触发,但是在之后的什么时候触发取决于事件队列。
c. 一般来说,通常没有抢占式的方式直接把事件放在队列之首。
模拟事件循环机制的执行
let eventLoop = [],event ; // 先进,先出while (true){if (eventLoop.length > 1){event = eventLoop.shift(); // 拿到事件队列中的第一个事件,每一个事件相当于一个 ticktry {event();}catch (err){new Error(err);}}}
3 - 任务循环
什么是任务循环
任务循环是在ES6中提出的新概念,可以理解为 当每一个 tick 执行时,任务会挂在每一个tick之后执行。在事件循环的每个tick中,可能触发的异步操作会挂在每一个tick列表最后,即当前tick执行结束后,执行任务。
4 - 并行和并发
- 并行线程:指能够同时发生的事情,多个线程能够共享单个进程的数据。但是在js中,事件循环把自身的工作分成一个个任务顺序执行,并不允许对共享内存进行并行访问和修改。
并发“进程”:两个或多个事件链随时间发展交替执行。但是同一时刻,一次只能从事件队列中处理一个事件。
- 非交互:互补影响
- 交互:产生竞态
- 协作:取得一个长期的进程,把他分隔成多个步骤或多批任务进行。 ```javascript // 模拟处理大数据协作 let res = []; function responseData(data){ let chunk = data.splice(0,1000);
res = res.concat( chunk.map(item => ++item) );
if (data.length > 0){ setTimeout(function (){
responseData(data)
},0); } } ```
二、常见的实现异步编程的方案
- 回调
- 事件监听
- Promise
- Generator
- async/await
三、回调
1 - 回调地狱
- 回调地狱伪代码
```javascript
// 嵌套回调 - 伪代码
addEventListener(‘click’,function handler(){
setTimeout(function request(){
ajax(url , function response(data){
}) },5000) })if (data === 'hello'){request()}else {handler()}
// 链式回调 - 伪代码 addEventListener(‘click’, handler); function handler(){ setTimeout(request,5000) } function request(){ ajax(url , response) } function response(data){ console.log(data); }
- **回调地狱的本质**- 我们需要在大量的逻辑代码中跳来跳去查看代码执行顺序- 需要硬编码处理回调函数在被调用的过程中会遇到的各种问题,而这种问题大多数情况下是不可复用的,所以使得代码更加难以理解和维护。- **回调地狱 - 回调函数的执行信任问题**```javascript// 回调地狱的信任问题 - 假设有一个第三方库 Ajax2,Ajax2(param , function ({a,b}){// 根据返回结果执行扣费函数})// 01 - 过早调用:没有a/b,扣费为0// 02 - 过晚调用/不调用:sum值已修改// 03 - 调用多次:sum被累加多次,扣费多次// 04 - 没有a或者b属性,扣费失败// 05 - 吞掉会出现的异常,扣费失败
四、Promise
1 - Promise机制
- 什么是promise
promise 是 ES6中提出的一种解决异步处理方案的方法,是一种封装和组合未来值的易于复用的机制。Promise一旦决议,它就变成了一个不可改变的值。
- 如何判断一个值是不是Promise
识别Promise(或者行为类似于Promise的东西),就是定义某种称之为thenable的东西。将其定义为任何具有then方法的对象和函数。任何这样的值就是和Promise行为一致的值。
// Promise 的链式调用let P = new Promise(((resolve, reject) => {// 处理一些事情resolve(2)}));P.then(v => {console.log(v); // 2return v * 2}).then(vv => {console.log(vv) // 4})
2 - Promise 的状态
Promise 本身的三种状态
- pending:初始状态,没有完成也没有被拒绝
- fulfilled:已成功完成的状态
- rejected:失败完成的状态
说明:Promise 的状态一旦发生改变,就不能再次修改,即内部状态的修改时不可逆的。
3 - Promise 的静态方法
//1.获取轮播数据列表function getBannerList(){return new Promise((resolve,reject)=>{setTimeout(function(){resolve('轮播数据')},300)})}//2.获取店铺列表function getStoreList(){return new Promise((resolve,reject)=>{setTimeout(function(){resolve('店铺数据')},500)})}//3.获取分类列表function getCategoryList(){return new Promise((resolve,reject)=>{setTimeout(function(){resolve('分类数据')},700)})}let promiseArray = [getBannerList() , getStoreList() , getCategoryList()];
- Promise.all
参数:可迭代的对象,例如Array
作用:当所有结果成功返回时按照请求顺序返回成功。当其中有一个失败方法时,则进入失败方法。
// 所有的Promise 都执行成功了才会走then方法,有一个失败了,就会走reject方法,参数是reject的原因let data1 = Promise.all(promiseArray).then(v=>{console.log(v) // [ '轮播数据', '店铺数据', '分类数据' ]}).catch(err => {console.log(err)});
- Promise.allSetted
参数:可迭代的对象
作用:不管执行成功与失败,都返回每一个参数的状态和执行结果
let data2 = Promise.allSettled(promiseArray).then(v => {console.log(v)}).catch(err=>{console.log(err)})
- Promise.any
参数:可迭代对象
作用: any 方法返回一个 Promise,只要参数 Promise 实例有一个变成 fulfilled 状态,最后 any 返回的实例就会变成 fulfilled 状态;如果所有参数 Promise 实例都变成 rejected 状态,包装实例就会变成 rejected 状态
let data3 = Promise.any(promiseArray).then(v=>{console.log(v);}).catch((err) => {console.log(err)})
- Promise.race
参数:可迭代对象
作用: race 方法返回一个 Promise,只要参数的 Promise 之中有一个实例率先改变状态,则 race 方法的返回状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 race 方法的回调函数。
用途:设置超时时间,判断一些资源是否在规定时间内加载成功。
// 哪个Promise先获取到结果,则整个Promise的状态都被决议无法修改let getImage = function (){return new Promise((resolve, reject) => {setTimeout(function (){resolve('图片请求成功')},1000)})}let timeOut = function (){return new Promise(((resolve, reject) => {setTimeout(function (){resolve('图片加载失败');},500)}))}let ImageResult = Promise.race([getImage(),timeOut()]).then(v => {console.log(v) //图片加载失败}).catch((err) => {console.log(err)})
4 - Promise 链式流
- 能够形成链式流的原因
- 每次执行.then() 函数都会自动创建一个新的Promise并返回
- 在处理函数内部,如果返回一个值或者抛出一个异常,新连接的Promise会立即被决议
- 如果内部处理函数返回的是一个Promise 或者 thenable,那它将会展开代替自动创建的Promise。
- 如何在链式中引入异步操作 ```javascript let p1 = new Promise((resolve, reject) => { setTimeout(function () { console.log(“500ms 后执行”); resolve(500) },500) })
p1.then(v => { console.log(“我等待了500ms才执行”); console.log(v) return v *2 })
// 500ms 后执行 // 我等待了500ms才执行 // 500
- **解决回调地狱问题**链式流通过把嵌套的回调函数形式修改成了链式调用,解决了回调函数产生的回调地狱的问题。<a name="mRIlI"></a>## 五、Generator<a name="HqOrl"></a>### 1 - Generator 简介- **什么是Generator**Generator 是一种带*的函数,但是又不是一种函数。他需要配合yield关键字使用,控制函数的执行顺序。```javascriptfunction* gen() {let a = yield 111;console.log(a);let b = yield 222;console.log(b);let c = yield 333;console.log(c);let d = yield 444;console.log(d);}let t = gen(); // 执行被阻塞,不会执行任何语句t.next(1); // 程序继续执行,遇到yield关键字后停止。t.next(2); // next 使得函数继续执行,a输出next参数2,遇到yield又暂停t.next(3); // b输出3;t.next(4); // c输出4;t.next(5); // d输出5;
- Generoter的执行关键
- 第一步:调用* 函数后,函数不会立即执行,程序被阻塞住
- 第二步:调用next方法,程序向下继续执行,遇到yield被暂停
- 第三步:直到返回的done为true后,执行结束。
- yield关键字
next执行后返回的结果为一个对象,格式为{value:yield关键字后的值,done:是否结束};执行next()函数时传进入的参数作为yield执行后的结果。
function* gen() {let a = yield function (){return 1};console.log(a); // undefinedlet b = yield 222;console.log(b);let c = yield 333;console.log(c);let d = yield 444;console.log(d);}let t = gen();console.log(t.next()); // { value: [Function], done: false }console.log(t.next()) // { value: 222, done: false }
2 - Generator + thunk 函数
- 什么是thunk函数
对一个具有公共功能的函数进行的封装,接受一个参数,返回一个可接受参数的含有定制功能的函数。
Generator + thunk 函数实现异步功能
// Generator + thunkfunction thunk(fileName){return (callBack) => {fs.readFile(fileName,callBack)}}function * fileGen(){const a = yield thunk("1.txt");console.log(a); // <Buffer e6 96 87 e6 a1 a3 31>const b = yield thunk("2.txt");console.log(b) // <Buffer e6 96 87 e6 a1 a3 e4 ba 8c>}let fileG = fileGen();fileG.next().value((err, data1) => {fileG.next(data1).value((err,data2) => {fileG.next(data2)})})
定义递归自执行的函数
function run(gen){const cb = function (err,data){let res = gen.next(data);if (res.done) return ;res.value(cb)}cb()}run(fileG);
3 - Generator + Promise
```javascript // Generator + Promise function promiseThunk(fileName){ return new Promise((resolve, reject) => { fs.readFile(fileName,(err,data) => {
if (err){reject(err);}else {resolve(data)}
}) }) }
function * gen2(){ const data1 = yield promiseThunk(“1.txt”) console.log(data1); const data2 = yield promiseThunk(‘2.txt’); console.log(data2) } function run2(g){ let gen = g(); let cb = function (err,data){ let res = gen.next(data); if (res.done) return res.value; res.value.then(function (data){ cb(err,data) }) } cb(); } run2(gen2);
<a name="Jq1wx"></a>## 六、Async / Await<a name="dZ9fT"></a>### 1 - 基础用法- async/await 中内置了执行器,不用手动指定next- 相比于 *,yield,语义更加明确- async函数执行后返回一个Promise- 适用性更广:yield后面只能是thunk函数或Promise对象,而await关键字后可以是Promise或者其他基础类型。```javascript// 读取文件function readFile(filename){return new Promise((resolve, reject) =>{if (filename){fs.readFile(filename,(err,data) => {if (err) reject(err);resolve(data)})}})}async function readFiles(){let file1 = await readFile("1.txt");let file2 = await readFile("2.txt");console.log(file1.toString());console.log(file2.toString());return "读取成功"}readFiles().then(res => console.log(res));
2 - 错误处理
- 如果await后面的函数报错,则等同于async函数返回的promise被reject
- 为了防止await函数报错,建议await函数放在try-catch中
- 多个await 可以统一放在try-catch中
3 - 使用注意点
因为await命令后面的函数可能会被reject,所以最好把await放到try-catch中
try{file1 = await readFile("1.txt");}catch (err){console.log(err)}
多个await命令后面的异步操作,如果不存在关联关系,建议同时触发
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
await 命令只能应用在async函数中,如果用在普通函数中,就会报错。
- async函数会保留上下文执行栈。
4 - async 实现原理
async 实现的基础时内置了执行器,就是将 Generator 函数和自动执行器,包装在一个函数里。 ```javascript async function fn(args) { // … }
// 等同于
function fn(args) { return spawn(function* () { // … }); }
```javascriptfunction spawn(gen){const g = gen();return new Promise((resolve, reject) => {const step = function (data){let result;try {result = g.next(data)}catch (e) {return reject(e)}if (result.done){return resolve(result.value)}Promise.resolve(result.value).then(res => {step(res)},function (err){return gen.throw(err)})}step();})}
七、异步总结

