js-异步机制与同步机制

Javascript的优势之一是其如何处理异步代码。异步代码会被放入一个事件队列,等到所有其他代码执行后才进行,而不会阻塞线程

1 理解异步代码:

1.1 JavaScript最基础的异步函数是setTimeout和setInterval。setTimeout会在一定时间后执行给定的函数。它接受一个回调函数作为第一参数和一个毫秒时间作为第二参数。

console.log(1);

setTimeout(function() {
  console.log('a');
},1000);
setTimeout(function() {
  console.log('b');
},1000);
setTimeout(function() {
  console.log('c');
},1000);
console.log(2);

正如预期,控制台先输出1、2,大约500毫秒后,再看到“a”、“b”、“c”。我用“大约”是因为setTimeout事实上是不可预知的。实际上,甚至 HTML5规范都提到了这个问题:

“这个API不能保证计时会如期准确地运行。由于CPU负载、其他任务等所导致的延迟是可以预料到的。”

1.2 Event Loop队列

有趣的是,直到在同一程序段中所有其余的代码执行结束后,超时才会发生。所以如果设置了超时,同时执行了需长时间运行的函数,那么在该函数执行完成之前,超时甚至都不会启动。实际上,异步函数,如setTimeout和setInterval,被压入了称之为Event Loop的队列。

Event Loop是一个回调函数队列。当异步函数执行时,回调函数会被压入这个队列。JavaScript引擎直到异步函数执行完成后,才会开始处理事件循环。这意味着JavaScript代码不是多线程的,即使表现的行为相似。事件循环是一个先进先出(FIFO)队列,这说明回调是按照它们被加入队列的顺序执行的。

2 JQuery异步处理

异步Javascript与XML(AJAX)永久性的改变了Javascript语言的状况。突然间,浏览器不再需要重新加载即可更新web页面。 在不同的浏览器中实现Ajax的代码可能漫长并且乏味. 但是有些地方还是需要注意

var data;
$.ajax({
    url: "some/url/1",
    success: function( data ) {
        // 放在jquery指定的success函数里面可以保证异步请求完成
        console.log( data );
    }
})
// 这里并不能获取数据 ajax异步请求还未完成
console.log( data );

容易犯的错误,是在调用$.ajax之后马上使用data,但是实际上是这样的

xmlhttp.open( "GET", "some/ur/1", true );
xmlhttp.onreadystatechange = function( data ) {
    if ( xmlhttp.readyState === 4 ) {
        console.log( data );
    }
};
xmlhttp.send( null );

底层的XmlHttpRequest对象发起请求,设置回调函数用来处理XHR的readystatechnage事件。
然后执行XHR的send方法。在XHR运行中,当其属性readyState改变时readystatechange事件就会被触发,
只有在XHR从远端服务器接收响应结束时回调函数才会触发执行。

3 回调函数--处理异步

异步编程很容易陷入我们常说的“回调地狱”。因为事实上几乎JS中的所有异步函数都用到了回调,连续执行几个异步函数的结果就是层层嵌套的回调函数以及随之而来的复杂代码。

eg: Nodejs中常见异步函数

var fs = require( "fs" );
fs.exists( "index.js", function() { // 回调函数处理异步
    fs.readFile( "index.js", "utf8", function( err, contents ) { // 回调函数处理异步
        contents = someFunction( contents ); // do something with contents
        fs.writeFile( "index.js", "utf8", function() { // 回调函数处理异步
            console.log( "全部按顺序执行完" );
        });
    });
});
console.log( "executing..." );

3.1 清除嵌套回调 

3.1.1 命名函数避免双层嵌套,解决嵌套回调

清除嵌套回调的一个便捷的解决方案是简单的避免双层以上的嵌套。传递一个命名函数给作为回调参数,而不是传递匿名函数:

4 事件--处理异步

事件是另一种当异步回调完成处理后的通讯方式。一个对象可以成为发射器并派发事件,而另外的对象则监听这些事件。这种类型的事件处理方式称之为 观察者模式 

5 Promise(ES6)--处理异步

// 实例化 Promise对象
let time = function(time) {
    return new Promise((resolve, reject) => {
       console.log('Promise实例化完成'); // Promise实例化后立即执行
       if (time >= 3000) {
           setTimeout(function(){
               resolve('大于3秒的时间后才显示出来');
           }, time)
       } else {
           reject('时间不能小于3秒')
       }
    });
};

// 实例化Promise对象后才能使用 then
time(1000).then((value) => { // 1000 会走第二条判断Reject(Pending => Reject 失败会走第二个回调)
    console.log(value);
}, (error) => {
    console.log(error);
});
console.log(1); // 'Promise实例化完成' 1 '时间不能小于3秒'

 

6 ES7的Async/Await--处理异步

var sleep = function (time) {
  return new Promise(function(resolve, reject) { // 返回一个promise对象
   setTimeout(function() {
      resolve();
    }, time)
  })
};
var start = async function() {
  console.log('开始');
  await sleep(3000); // 等待异步过程完成再往下执行
  console.log('结束');
}
start();

// 控制台先输出start,稍等3秒后,输出了end。
基本规则
async 表示这是一个async(异步)函数,await只能用在这个函数里面。
await 表示在这里等待promise返回结果了,再继续执行。
await 后面跟着的应该是一个promise对象(当然,其他返回值也没关系,只是会立即执行,不过那样就没有意义了…)
获得返回值

await等待的虽然是promise对象,但不必写.then(..),直接可以得到返回值。

var sleep = function (time) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            // 返回 ‘ok’
            resolve('ok');
        }, time);
    })
};

var start = async function () {
    let result = await sleep(3000);
    console.log(result); // 收到 ‘ok’
};
// 获取异步结果
let sleep = function (time) {
    return new Promise(function(resolve, reject){
        setTimeout(function() {
            resolve('异步返回结果');
        }, time)
    });
};

let start = async function() {
  let result = await sleep('3000');
  console.log(result);
};
start(); // 3秒后输出 '异步返回结果'
// ** 捕获错误
var asyncFn = function (time) {
    return new Promise((resolve, reject) => {
        setTimeout(function() {
            reject('异步出了问题');
        }, time)
    });
};


var start = async function() {
  console.log('开始');
  let result = await asyncFn(3000); // 返回了一个错误,不会往下执行了
  console.log(result);
  console.log('结束');
};
// 开始
// (node:2200) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): 异步出了问题
// (node:2200) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

// 既然.then(..)不用写了,那么.catch(..)也不用写,可以直接用标准的try catch语法捕捉错误。
var start = async function() {
    try{
        console.log('开始');
        let result = await asyncFn(3000); // 返回了一个错误,不会往下执行了
        console.log(result);
        console.log('结束');
    } catch(err) { //出了错误
        console.log(err)
    }
};
// 开始
// 异步出了问题

start();

 参考资料

http://cnodejs.org/topic/5640b80d3a6aa72c5e0030b6

问题: 

1 Promise是不是还是使用回调函数方式处理异步,但是避免了多重回调嵌套的问题?

  An: Promise是处理异步的一种方式和回调函数处理异步方式是平行关系

 

posted @ 2017-06-01 11:09  Jesonhu  阅读(5861)  评论(0编辑  收藏  举报
Top