生活小调

浅谈 Javascript 异步编程

2020-06-20  本文已影响0人  望月从良glh
异步编程

单线程 JavaScript

目前主流的 JavaScrip 环境都是以单线程模式执行的 javaScript 代码

采用单线程的原因

JS 在最初只是一门运行在浏览器端的脚本语言,负责处理页面动态交互,而实现页面交互的核心是 DOM 操作,这也就决定了必须是单线程,否则就会出现复杂的线程同步问题。
假如当操作 Dom 的时候如果同时多个线程工作,在一个线程中删除一个 DOM,而另一个线程删除一个 DOM,浏览器就不知道到底要按照那个线程的工作结果为准。因此 JavaScript 被设计成单线程模式,也是这门语言最为核心的特性之一

什么是单线程

JS 执行环境中负责执行代码的线程只有一个。通俗的说,单线程就是一个人同一时间只做一个任务,如果有多个任务就必须要排队执行,只有当前任务做完才回去做下一个任务。在下面代码中

document.write('task 1')
document.write('task 2')
document.write('task 3')
document.write('task 4')
document.write('task 5')

当 js 引擎加载的时候,因为 JavaScript 引擎 只有一个线程在处理代码。所以从上到下依次同步执行

单线程的优缺点

console.log('foo')
for (let i = 0; i < 100000; i++) {
  console.log('耗时操作 ')
}
console.log('等待耗时操作结束')

为了解决 JavaScript 这种阻塞耗时的问题,将任务的执行模式分成两种

同步模式和异步模式

什么是同步模式

同步模式就是 js 代码依次执行代码,后面的代码排队等候。程序的执行顺序与我们代码的执行顺序完全一致。简单点,同步不是'同时',而是'排队'

console.log('global begin')

function bar () {
  console.log('bar task')
}

function foo () {
  console.log('foo task')
  bar()
}

foo()

console.log('global end')

什么是异步模式

异步模式就是代码执行到异步任务的时候下达一个指令,单独开启一个线程(并非 js 线程)去执行异步任务,js 线程继续按照同步模式往下执行,并在本轮执行结束后,加载异步任务的回调函数。

console.log('global begin')

setTimeout(function timer1 () {
  console.log('timer1 invoke')
}, 1800)

setTimeout(function timer2 () {
  console.log('timer2 invoke')

  setTimeout(function inner () {
    console.log('inner invoke')
  }, 1000)
}, 1000)

console.log('global end')

EventLoop

当调用栈(正在执行的工作表)结束本轮执行,事件循环就会监听到,同时监听消息队列(待办工作表),如果消息队列有异步回调,就会从消息队列取出第一个回调函数压入到调用栈,以此循环直到所有任务被执行完。

消息队列

当异步任务执行完成就会将异步任务的回调函数放到消息队列中等待下一轮事件循环

总结

异步执行过程

回调函数

所有异步编程的根基

定义: 由调用者定义,交给执行者执行的函数
理解:回调函数就是一件想要做的事情,但是你并不知道这件事情所依赖的任务什么时候完成。所以最好的办法就是把这件事的步骤放到函数中,交给任务的执行者去执行,因为执行者是知道这件事情什么时候完成的。

// 回调函数
function foo (callback) {
  setTimeout(function () {
    callback()
  }, 3000)
}

foo(function () {
  console.log('这就是一个回调函数')
  console.log('调用者定义这个函数,执行者执行这个函数')
  console.log('其实就是调用者告诉执行者异步任务结束后应该做什么')
})

Promise

一种更优的异步编解决方案

CommonJS 社区提出了 Promise 规范,主要解决回调地狱问题,在 ES2015 中被标准化

// 回调地狱,只是示例,不能运行
$.get('/url1', function (data1) {
  $.get('/url2', data1, function (data2) {
    $.get('/url3', data2, function (data3) {
      $.get('/url4', data3, function (data4) {
        $.get('/url5', data4, function (data5) {
          $.get('/url6', data5, function (data6) {
            $.get('/url7', data6, function (data7) {
              // 略微夸张了一点点
            })
          })
        })
      })
    })
  })
})

Promise 的基本使用

一张图看懂
// Promise 基本示例

const promise = new Promise(function (resolve, reject) {
  // 这里用于“兑现”承诺

  // resolve(100) // 承诺达成

  reject(new Error('promise rejected')) // 承诺失败
})

promise.then(function (value) {
  // 即便没有异步操作,then 方法中传入的回调仍然会被放入队列,等待下一轮执行
  console.log('resolved', value)
}, function (error) {
  console.log('rejected', error)
})

console.log('end')

常见误区

// Promise 常见误区

function ajax (url) {
 return new Promise(function (resolve, reject) {
   var xhr = new XMLHttpRequest()
   xhr.open('GET', url)
   xhr.responseType = 'json'
   xhr.onload = function () {
     if (this.status === 200) {
       resolve(this.response)
     } else {
       reject(new Error(this.statusText))
     }
   }
   xhr.send()
 })
}

// 嵌套使用 Promise 是最常见的误区
 ajax('/api/urls.json').then(function (urls) {
   ajax(urls.users).then(function (users) {
    ajax(urls.users).then(function (users) {
       ajax(urls.users).then(function (users) {
         ajax(urls.users).then(function (users) {
            // 这种方式偏离的promise的设计初心,还是会有回调'地狱'的问题
         })
       })
     })
   })
 })

链式调用

Promise的then或者catch方法每次返回新的promise对象

// Promise 链式调用

function ajax (url) {
  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = function () {
      if (this.status === 200) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

// var promise = ajax('/api/users.json')

// var promise2 = promise.then(
//   function onFulfilled (value) {
//     console.log('onFulfilled', value)
//   },
//   function onRejected (error) {
//     console.log('onRejected', error)
//   }
// )

// console.log(promise2 === promise)

ajax('/api/users.json')
  .then(function (value) {
    console.log(1111)
    return ajax('/api/urls.json')
  }) // => Promise
  .then(function (value) {
    console.log(2222)
    console.log(value)
    return ajax('/api/urls.json')
  }) // => Promise
  .then(function (value) {
    console.log(3333)
    return ajax('/api/urls.json')
  }) // => Promise
  .then(function (value) {
    console.log(4444)
    return 'foo'
  }) // => Promise
  .then(function (value) {
    console.log(5555)
    console.log(value)
  })

异常处理

// Promise 异常处理

function ajax (url) {
  return new Promise(function (resolve, reject) {
    // foo()
    // throw new Error()
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = function () {
      if (this.status === 200) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}
 ajax('/api/users11.json') // 当前返回的promise
   .then(function onFulfilled (value) {
     console.log('onFulfilled', value)
   }, function onRejected (error) {
     console.log('onRejected', error)
   })
 ajax('/api/users11.json')
   .then(function onFulfilled (value) {
     console.log('onFulfilled', value)
   })
   .catch(function onRejected (error) {
     console.log('onRejected', error)
   })

Node.js 中使用以下方式

process.on('unhandledRejection', (reason, promise) => {
   console.log(reason, promise)
   // reason => Promise 失败原因,一般是一个错误对象
   // promise => 出现异常的 Promise 对象
 })

执行时序

console.log(1)
setTimeout(()=>{  // 宏任务
console.log(2)
},0)
Promise.resovle().then(()=>{
  console.log(3)
})
console.og(4) // 1 4 3 2

Generator

Promise 的链式调用还是没有达到传统同步代码的可读性。Generator 就是为了让异步代码采用同步方式去编写。

// generator生成器函数
function* foo() {
  try {
    let res = yield 'hello';
    console.log(res);
  } catch (e) {
    console.log(e);
  }
}
// 生成 generator 对象
const generator = foo();
console.log(generator.next('f'));
console.log(generator.throw('err'));

利用生成器完成更优的异步编程体验

// const ajax = value => {
  return new Promise((resovle, rej) => {
    setTimeout(() => {
      resovle(value);
    }, 1000);
  });
};

function* mian() {
  const name = yield ajax('guolihang');
  console.log(name); // 彻底消灭Promise的回调 近乎于同步的编程
  const age = yield ajax('18');
  console.log(age);
  console.log(2334);
}

 const result = g.next();
 result.value.then(name => {
   if (result.done) return;
   const result2 = g.next(name);
   result2.value.then(age => {
     console.log(result2.done);
     if (result2.done) return;
     g.next(age);
     // .... 采用递归
   });
 });

采用递归的方式实现生成器函数执行器
co库在15年之前就是这样分装 async/await 的实现机制也是类似

function co(generator) {
  const g = generator();
   function handleResult(result) {
     if (result.done) return;
     result.value.then(
       value => {
         handleResult(g.next(value));
       },
       err => {
         g.throw(err);
       },
     );
   }
   handleResult(g.next());
 }
 co(mian);

async/await

async await 是对 Generator 的封装,内部提供了生成器函数的执行器方法,并帮我们自动调用。
async/await的内部实现 参考 阮一峰ES6

// async await 的内部实现方式
async function fn(args) {
  // ...
}

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

function spawn(genF) {
  return new Promise(function (resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch (e) {
        return reject(e);
      }
      if (next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(
        function (v) {
          step(function () {
            return gen.next(v);
          });
        },
        function (e) {
          step(function () {
            return gen.throw(e);
          });
        }
      );
    }
    step(function () {
      return gen.next(undefined);
    });
  });
}

学习完再加餐撸个面试题,美滋滋~~

console.log('AAAA');
setTimeout(() => console.log('BBBB'), 1000); // t1
const start = new Date();
while (new Date() - start < 3000) {}
console.log('CCCC');
setTimeout(() => console.log('DDDD'), 0);  // t2
new Promise((resolve, reject) => {
  console.log('EEEE');
  foo.bar(100);
})
  .then(() => console.log('FFFF'))
  .then(() => console.log('GGGG'))
  .catch(() => console.log('HHHH'));
console.log('IIII');

结果

AAAA => CCCC => EEEE => IIII => HHHH => BBBB => DDDD

分析:
这道题重点考察了js异步编程,宏任务,微任务。

我们先找出线程上的同步代码,将结果依次排列出来, AAA CCC EEEE IIII
然后我们再找出所有异步任务中的微任务把結果打印出来HHHH
最后我们再找出异步中的所有宏任务,这里t1排在前面,t2排在后面(这个原因是while造成的) ,输出结果顺序是BBBB DDDD
所以综上结果是AAAA => CCCC => EEEE => IIII => HHHH => BBBB => DDDD

上一篇下一篇

猜你喜欢

热点阅读