浅谈js异步编程

127 阅读2分钟

浅谈js异步编程

js语言是单线程的

同步任务:主线程上排队执行的任务,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。

异步任务:不进入主线程、而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。所以说同步执行其实也是一种只有主线程的异步执行。

定时器:

setTimeOut(function(){
   // 回调函数
}, 1000)
// 示例一
var fn = function() {
    let num = Math.round(Math.random() * 100) / 100;
    console.log(num);
    setTimeout(function() {
    	if (num < 0.5) {
        	return ('1234567');
        } else {
        	return ('abcdefg');
        }
    }, 500)
}
console.log(fn())

// 0.68 undefined

上面例子中setTimeOut为异步函数,执行的console.log(res())是同步的,不会等待计时器函数的执行。

回调函数:是一个作为变量传递给另外一个函数的函数,它在主体函数执行完之后执行。

简单的回调函数例子:

function fn(arg1, arg2, callback){
    var num = Math.ceil(Math.random() * (arg1 - arg2) + arg2);
    callback(num);  //传递结果
}

fn(10, 20, function(num){
    console.log("Callback called! Num: " + num); 
});
//结果为10和20之间的随机数

使用回调函数解决示例一的问题:

var fn = function(res) {
	let num = Math.round(Math.random() * 100) / 100;
    console.log(num);
    setTimeout(function() {
    	if (num < 0.5) {
        	res('1234567');
        } else {
        	res('abcdefg');
        }
    }, 500)
}
var result = fn(function(res1) {
  	console.log(res1)
})

// 0.43  1234567

回调地狱:多层嵌套的回调函数

var sayhello = function (name, callback) {
  setTimeout(function () {
    console.log(name);
    callback();
  }, 1000);
}
sayhello("first", function () {
  sayhello("second", function () {
    sayhello("third", function () {
      console.log("end");
    });
  });
});

// first second third  end
  • 代码阅读困难!

  • 不能用 try catch 捕获错误,不能 return

  • 调试困难,很难处理错误

  • 耦合性过高,一旦有所改动,就会牵一发而动全身

**Promise对象:**解决了回调地狱,为异步操作提供统一接口。它起到代理作用,充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口。Promise可以让异步操作写起来 就像在写同步操作的流程,而不必一层层地嵌套回调函数。

promise对象的创建与使用

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

promise对象的链式调用解决了回调地狱,每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装

var sayhello = function (name) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log(name);
      resolve();  //在异步操作执行完后执行 resolve() 函数
    }, 1000);
  });
}
sayhello("first").then(function () {
  return sayhello("second");  //仍然返回一个 Promise 对象
}).then(function () {
  return sayhello("third");
}).then(function () {
  console.log('end');
}).catch(function (err) {
  console.log(err);
})

// first  second  third  end

使用Promise对象解决示例一的问题:

const fn = function() {
    var _this = this;
    let num = Math.round(Math.random() * 100) / 100;
    console.log(num);
    return new Promise((resolve, reject) => {
        setTimeout(function() {
            num < 0.5 ? resolve('1234567') : reject('abcdefg')
        }, 500);
    })
}
fn()
    .then(val => {console.log(val);})
    .catch(err => {console.log(err)});

// 0.86  abcdefg

特点:Promise只会有一个解析结果:完成或拒绝,并且无法取消。

**Async/await:**是异步的终极解决方案

解决示例一的问题:

const fn = async function () {
    let num = Math.round(Math.random() * 100) / 100;
    console.log(num);
    const result = await aa(num);
}
function aa(num) {
    setTimeout(() => {
        console.log(num < 0.5 ? '1234567' : 'abcdefg')
    }, 500)
}
fn();

// 0.88 1234567

解决回调地狱:

var sayhello = function (name) {
  setTimeout(function () {
    console.log(name);
  }, 1000);
}

async function fn() {
  await sayhello('first') 
  await sayhello('second') 
  await sayhello('third') 
  await sayhello('end') 
}
fn();

// first  second  third  end

await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低;如果有依赖性,就是解决回调地狱的方式。