一、异步:现在与将来

1 - 异步机制

  • 什么是异步机制

a. 对于一段js代码,主要分为两块,一块是现在执行,一块是将来执行。
b. 一旦把一部分代码包装成一个函数,并指定它在响应某个事件时执行,那就形成了一个将来时代码块,同时也引入了异步机制

2 - 事件循环机制

  • 什么是事件循环机制

a. js 引擎本身做的事情是:在需要的时候,在给定的任意时间段执行单个代码块。即 js 引擎本身并没有时间概念,只是按需执行js的代码片段。
b. js 引擎运行的宿主环境都提供了一种机制:能够处理程序中多个块的执行,并且在每个块执行时都调用js 引擎。这种机制被称为 事件循环机制。

  • 关于setTimeout的异步说明

a. 在执行到 setTimeout() 时,并没有立刻把回调函数挂在事件循环队列中,它只是设定了一个定时器。当定时器结束后,宿主环境会把回调函数放在事件循环队列中。
b. 如果计时器结束后,事件循环队列中有20个项目在等待,那回调函数就被放在20个项目之后,当前面20个项目执行结束后,才会触发回调函数。即:定时器只能保证事件不会在设定的时间之前触发,但是在之后的什么时候触发取决于事件队列
c. 一般来说,通常没有抢占式的方式直接把事件放在队列之首。

  • 模拟事件循环机制的执行

    1. let eventLoop = [],event ; // 先进,先出
    2. while (true){
    3. if (eventLoop.length > 1){
    4. event = eventLoop.shift(); // 拿到事件队列中的第一个事件,每一个事件相当于一个 tick
    5. try {
    6. event();
    7. }catch (err){
    8. new Error(err);
    9. }
    10. }
    11. }

    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 (){

    1. responseData(data)

    },0); } } ```

二、常见的实现异步编程的方案

  • 回调
  • 事件监听
  • Promise
  • Generator
  • async/await

三、回调

1 - 回调地狱

  • 回调地狱伪代码 ```javascript // 嵌套回调 - 伪代码 addEventListener(‘click’,function handler(){ setTimeout(function request(){ ajax(url , function response(data){
    1. if (data === 'hello'){
    2. request()
    3. }else {
    4. handler()
    5. }
    }) },5000) })

// 链式回调 - 伪代码 addEventListener(‘click’, handler); function handler(){ setTimeout(request,5000) } function request(){ ajax(url , response) } function response(data){ console.log(data); }

  1. - **回调地狱的本质**
  2. - 我们需要在大量的逻辑代码中跳来跳去查看代码执行顺序
  3. - 需要硬编码处理回调函数在被调用的过程中会遇到的各种问题,而这种问题大多数情况下是不可复用的,所以使得代码更加难以理解和维护。
  4. - **回调地狱 - 回调函数的执行信任问题**
  5. ```javascript
  6. // 回调地狱的信任问题 - 假设有一个第三方库 Ajax2,
  7. Ajax2(param , function ({a,b}){
  8. // 根据返回结果执行扣费函数
  9. })
  10. // 01 - 过早调用:没有a/b,扣费为0
  11. // 02 - 过晚调用/不调用:sum值已修改
  12. // 03 - 调用多次:sum被累加多次,扣费多次
  13. // 04 - 没有a或者b属性,扣费失败
  14. // 05 - 吞掉会出现的异常,扣费失败

四、Promise

1 - Promise机制

  • 什么是promise

promise 是 ES6中提出的一种解决异步处理方案的方法,是一种封装和组合未来值的易于复用的机制。Promise一旦决议,它就变成了一个不可改变的值。

  • 如何判断一个值是不是Promise

识别Promise(或者行为类似于Promise的东西),就是定义某种称之为thenable的东西。将其定义为任何具有then方法的对象和函数。任何这样的值就是和Promise行为一致的值。

  1. // Promise 的链式调用
  2. let P = new Promise(((resolve, reject) => {
  3. // 处理一些事情
  4. resolve(2)
  5. }));
  6. P.then(v => {
  7. console.log(v); // 2
  8. return v * 2
  9. }).then(vv => {
  10. console.log(vv) // 4
  11. })

2 - Promise 的状态

Promise 本身的三种状态

  • pending:初始状态,没有完成也没有被拒绝
  • fulfilled:已成功完成的状态
  • rejected:失败完成的状态

    说明:Promise 的状态一旦发生改变,就不能再次修改,即内部状态的修改时不可逆的。

3 - Promise 的静态方法

  1. //1.获取轮播数据列表
  2. function getBannerList(){
  3. return new Promise((resolve,reject)=>{
  4. setTimeout(function(){
  5. resolve('轮播数据')
  6. },300)
  7. })
  8. }
  9. //2.获取店铺列表
  10. function getStoreList(){
  11. return new Promise((resolve,reject)=>{
  12. setTimeout(function(){
  13. resolve('店铺数据')
  14. },500)
  15. })
  16. }
  17. //3.获取分类列表
  18. function getCategoryList(){
  19. return new Promise((resolve,reject)=>{
  20. setTimeout(function(){
  21. resolve('分类数据')
  22. },700)
  23. })
  24. }
  25. let promiseArray = [getBannerList() , getStoreList() , getCategoryList()];
  • Promise.all

参数:可迭代的对象,例如Array
作用:当所有结果成功返回时按照请求顺序返回成功。当其中有一个失败方法时,则进入失败方法。

  1. // 所有的Promise 都执行成功了才会走then方法,有一个失败了,就会走reject方法,参数是reject的原因
  2. let data1 = Promise.all(promiseArray).then(v=>{
  3. console.log(v) // [ '轮播数据', '店铺数据', '分类数据' ]
  4. }).catch(err => {
  5. console.log(err)
  6. });
  • Promise.allSetted

参数:可迭代的对象
作用:不管执行成功与失败,都返回每一个参数的状态和执行结果

  1. let data2 = Promise.allSettled(promiseArray).then(v => {
  2. console.log(v)
  3. }).catch(err=>{
  4. console.log(err)
  5. })
  • Promise.any

参数:可迭代对象
作用: any 方法返回一个 Promise,只要参数 Promise 实例有一个变成 fulfilled 状态,最后 any 返回的实例就会变成 fulfilled 状态;如果所有参数 Promise 实例都变成 rejected 状态,包装实例就会变成 rejected 状态

  1. let data3 = Promise.any(promiseArray).then(v=>{
  2. console.log(v);
  3. }).catch((err) => {
  4. console.log(err)
  5. })
  • Promise.race

参数:可迭代对象
作用: race 方法返回一个 Promise,只要参数的 Promise 之中有一个实例率先改变状态,则 race 方法的返回状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 race 方法的回调函数。
用途:设置超时时间,判断一些资源是否在规定时间内加载成功。

  1. // 哪个Promise先获取到结果,则整个Promise的状态都被决议无法修改
  2. let getImage = function (){
  3. return new Promise((resolve, reject) => {
  4. setTimeout(function (){
  5. resolve('图片请求成功')
  6. },1000)
  7. })
  8. }
  9. let timeOut = function (){
  10. return new Promise(((resolve, reject) => {
  11. setTimeout(function (){
  12. resolve('图片加载失败');
  13. },500)
  14. }))
  15. }
  16. let ImageResult = Promise.race([getImage(),timeOut()]).then(v => {
  17. console.log(v) //图片加载失败
  18. }).catch((err) => {
  19. console.log(err)
  20. })

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

  1. - **解决回调地狱问题**
  2. 链式流通过把嵌套的回调函数形式修改成了链式调用,解决了回调函数产生的回调地狱的问题。
  3. <a name="mRIlI"></a>
  4. ## 五、Generator
  5. <a name="HqOrl"></a>
  6. ### 1 - Generator 简介
  7. - **什么是Generator**
  8. Generator 是一种带*的函数,但是又不是一种函数。他需要配合yield关键字使用,控制函数的执行顺序。
  9. ```javascript
  10. function* gen() {
  11. let a = yield 111;
  12. console.log(a);
  13. let b = yield 222;
  14. console.log(b);
  15. let c = yield 333;
  16. console.log(c);
  17. let d = yield 444;
  18. console.log(d);
  19. }
  20. let t = gen(); // 执行被阻塞,不会执行任何语句
  21. t.next(1); // 程序继续执行,遇到yield关键字后停止。
  22. t.next(2); // next 使得函数继续执行,a输出next参数2,遇到yield又暂停
  23. t.next(3); // b输出3;
  24. t.next(4); // c输出4;
  25. t.next(5); // d输出5;
  • Generoter的执行关键
    • 第一步:调用* 函数后,函数不会立即执行,程序被阻塞住
    • 第二步:调用next方法,程序向下继续执行,遇到yield被暂停
    • 第三步:直到返回的done为true后,执行结束。
  • yield关键字

next执行后返回的结果为一个对象,格式为{value:yield关键字后的值,done:是否结束};执行next()函数时传进入的参数作为yield执行后的结果。

  1. function* gen() {
  2. let a = yield function (){
  3. return 1
  4. };
  5. console.log(a); // undefined
  6. let b = yield 222;
  7. console.log(b);
  8. let c = yield 333;
  9. console.log(c);
  10. let d = yield 444;
  11. console.log(d);
  12. }
  13. let t = gen();
  14. console.log(t.next()); // { value: [Function], done: false }
  15. console.log(t.next()) // { value: 222, done: false }

2 - Generator + thunk 函数

  • 什么是thunk函数

对一个具有公共功能的函数进行的封装,接受一个参数,返回一个可接受参数的含有定制功能的函数。

  • Generator + thunk 函数实现异步功能

    1. // Generator + thunk
    2. function thunk(fileName){
    3. return (callBack) => {
    4. fs.readFile(fileName,callBack)
    5. }
    6. }
    7. function * fileGen(){
    8. const a = yield thunk("1.txt");
    9. console.log(a); // <Buffer e6 96 87 e6 a1 a3 31>
    10. const b = yield thunk("2.txt");
    11. console.log(b) // <Buffer e6 96 87 e6 a1 a3 e4 ba 8c>
    12. }
    13. let fileG = fileGen();
    14. fileG.next().value((err, data1) => {
    15. fileG.next(data1).value((err,data2) => {
    16. fileG.next(data2)
    17. })
    18. })
  • 定义递归自执行的函数

    1. function run(gen){
    2. const cb = function (err,data){
    3. let res = gen.next(data);
    4. if (res.done) return ;
    5. res.value(cb)
    6. }
    7. cb()
    8. }
    9. run(fileG);

    3 - Generator + Promise

    ```javascript // Generator + Promise function promiseThunk(fileName){ return new Promise((resolve, reject) => { fs.readFile(fileName,(err,data) => {

    1. if (err){
    2. reject(err);
    3. }else {
    4. resolve(data)
    5. }

    }) }) }

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

  1. <a name="Jq1wx"></a>
  2. ## 六、Async / Await
  3. <a name="dZ9fT"></a>
  4. ### 1 - 基础用法
  5. - async/await 中内置了执行器,不用手动指定next
  6. - 相比于 *,yield,语义更加明确
  7. - async函数执行后返回一个Promise
  8. - 适用性更广:yield后面只能是thunk函数或Promise对象,而await关键字后可以是Promise或者其他基础类型。
  9. ```javascript
  10. // 读取文件
  11. function readFile(filename){
  12. return new Promise((resolve, reject) =>{
  13. if (filename){
  14. fs.readFile(filename,(err,data) => {
  15. if (err) reject(err);
  16. resolve(data)
  17. })
  18. }
  19. })
  20. }
  21. async function readFiles(){
  22. let file1 = await readFile("1.txt");
  23. let file2 = await readFile("2.txt");
  24. console.log(file1.toString());
  25. console.log(file2.toString());
  26. return "读取成功"
  27. }
  28. readFiles().then(res => console.log(res));

2 - 错误处理

  • 如果await后面的函数报错,则等同于async函数返回的promise被reject
  • 为了防止await函数报错,建议await函数放在try-catch中
  • 多个await 可以统一放在try-catch中

    3 - 使用注意点

  1. 因为await命令后面的函数可能会被reject,所以最好把await放到try-catch中

    1. try{
    2. file1 = await readFile("1.txt");
    3. }catch (err){
    4. console.log(err)
    5. }
  2. 多个await命令后面的异步操作,如果不存在关联关系,建议同时触发

    1. let [foo, bar] = await Promise.all([getFoo(), getBar()]);
  3. await 命令只能应用在async函数中,如果用在普通函数中,就会报错。

  4. async函数会保留上下文执行栈。

    4 - async 实现原理

    async 实现的基础时内置了执行器,就是将 Generator 函数和自动执行器,包装在一个函数里。 ```javascript async function fn(args) { // … }

// 等同于

function fn(args) { return spawn(function* () { // … }); }

  1. ```javascript
  2. function spawn(gen){
  3. const g = gen();
  4. return new Promise((resolve, reject) => {
  5. const step = function (data){
  6. let result;
  7. try {
  8. result = g.next(data)
  9. }catch (e) {
  10. return reject(e)
  11. }
  12. if (result.done){
  13. return resolve(result.value)
  14. }
  15. Promise.resolve(result.value).then(res => {
  16. step(res)
  17. },function (err){
  18. return gen.throw(err)
  19. })
  20. }
  21. step();
  22. })
  23. }

七、异步总结

异步处理.png