JS的三座大山——异步

869 阅读2分钟

异步

单线程

  • 只有一个线程,只能做一件事
  • 使用单线程可以避免DOM渲染的冲突
    • 浏览器需要渲染DOM
    • JS可以修改DOM结构
    • JS执行的时候,浏览器的DOM渲染就会暂停
    • 两段JS也不能同时执行(都修改DOM就冲突了)
    • webworker 支持多线程,但是不能访问DOM
  • 解决单线程的方案:使用异步

异步的缺点

  • callback 中不能模块化
  • 事件执行顺序不能够按照代码书写的方式执行

异步如何执行

  • 事件轮询是 JS 实现异步的具体解决方案
  • 同步的代码先执行
  • 异步函数先放到 异步队列
  • 待同步函数执行完毕,轮询执行异步队列中的函数

例子:

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

setTimeout(function() {
  console.log('b');
});

console.log('c');

// 最后输出的是 c b a

当页面开始加载,或者说js代码开始执行时。首先执行的是console.log('c');,其次将 console.log(b) 放入到异步队列中,最后过 1000 毫秒之后将 console.log('a') 放入到异步队列中,执行的过程中一直都在轮询去监听执行异步操作。用下面一张图进行解释。

异步队列中有报错,这个不用去管,只是理解这个轮询(event loop)的机制就好。

jQuery 中的 deferred

jQuery 1.5 之后有 deferred 的 API,jQuery 就可以顺利的解决异步 callback 回调地狱的问题了。

  • deferred 类似 promise 写法
  • 其中包括两类 API
    • 第一类:dtd.resolve dtd.reject
    • 第二类:dtd.then dtd.done detd.fail
  • 使用 $.when() 和 return 一个 promise 对象去解决直接调用 w.reject() 意向不到的错误。通过生成 promise 对象,创建只有被动监听,不能主动修改的环境。

直接上代码:

function waitHandle() {
  var dtd = $.Deferred()
  var wait = function (dtd) {
    var task = function () {
      console.log('执行完成');
      // 异步成功
      dtd.resolve();
      // 异步失败
      // dtd.reject();
    }
    setTimeout(task, 1000);
    return dtd.promise()
  }
  return wait(dtd)
}

var w = waitHandle();
// w.reject()   // 会直接报错
// 对扩展开放,对修改封闭
$.when(w).then(() => {
  console.log('ok 1');
}, () => {
  console.log('error 1');
}).then(() => {
  console.log('ok 2');
}, () => {
  console.log('error 2');
})

Promise

使用图片先后加载的例子解释 promise

function loadImg(src) {
  var promise = new Promise((resolve, reject) => {
    var img = document.createElement('img')
    // throw new Error('test')
    img.onload = function() {
      resolve(img)
    }
    img.onerror = function() {
      reject('图片不存在')
    }
    img.src = src
  })
  return promise
}

var src1 = 'http://images.coohua.com/upload/1bd6b717-8694-45f3-93a2-9766997524fe.jpg'
var result1 = loadImg(src1)
var src2 = 'http://images.coohua.com/upload/05139321-df16-44d4-bd35-55db7d298a421.jpg'
var result2 = loadImg(src2)

result1.then(img => {
  console.log('1,' + img.width);
  return result2
}).then(img => {
  console.log('2.' + img.width);
}).catch(error => {
  console.log(error);
})

解决异步的方法

  • jQuery Deferred
  • promise
  • async/await