Skip to content

同步和异步

javaScript 语言的执行环境是单线程,也就是指一次只能完成一个任务,如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务

JavaScript 语言将任务的执行模式分为两种:同步(Synchronous, sync)和异步(Asynchronous, async

同步不意味着所有步骤同时运行,而是指步骤在一个控制流序列中按顺序执行

异步的概念则是不保证同步的概念,一个异步过程的执行将不再与原有的序列有顺序关系

在前端开发中,为了避免主线程阻塞,我们常常用子线程来完成一些可能消耗时间足够长已至于被用户察觉的事情,比如读取一个大文件,或者发出一个网络请求。因为子线程独立于主线程,所以即使出现阻塞也不会影响主线程的运行。但是子线程有一个局限:一旦发射了以后,就会与主线程失去同步,我们无法确定它的结束,如果结束之后需要处理其他事情,比如处理来自服务器的信息,我们是无法将其合并到主线程中去的

为了解决上述问题,JavaScript 中的异步操作函数往往通过回调函数来实现异步任务的结果处理


回调

回调函数就是一个函数,它是在我们启动一个异步任务的时候就告诉它:等你完成了这个任务之后要干什么

回调 (callback) 是作为参数传递给另一个函数的函数,这种技术允许函数调用另一个函数,回调函数可以在另一个函数完成后运行

js
function myDisplayer(some) {
  document.getElementById("demo").innerHTML = some;
}

function myCalculator(num1, num2, myCallback) {
  let sum = num1 + num2;
  myCallback(sum);
}
// 当您将函数作为参数传递时,请记住不要使用括号
myCalculator(5, 5, myDisplayer);  // myDisplayer 是函数的名称,作为参数传递给 myCalculator()

回调真正闪光之处是异步函数,其中一个函数必须等待另一个函数(例如等待文件加载)


异步

异步:与其他函数并行运行的函数称为异步,回调最常与异步函数一起使用

常见的异步操作:

  1. Ajax请求一般采用异步,也可以设置成同步
  2. setTimeoutsetInterval
  3. Promise.then
  4. 回调函数可以理解成异步,但不是严格的异步操作
  5. 事件监听监听某个事件,当事件发生时,再执行相应操作

等待超时

js
// 使用 JavaScript 函数 setTimeout() 时,可以指定超时时执行的回调函数
setTimeout(myFunction, 3000);  // 3000表示等待3秒,整个表示等待3秒后。执行myFunction函数

function myFunction() {
  document.getElementById("demo").innerHTML = "I love You !!";
}

等待间隔

使用JavaScript 函数 setInterval() 时,可以指定每个间隔执行的回调函数

js
setInterval(myFunction, 1000);   //表示1秒执行一次myFunction函数

等待文件

创建函数来加载外部资源(如脚本或文件),则在内容完全加载之前无法使用这些内容

js
function myDisplayer(some) {
  document.getElementById("demo").innerHTML = some;
}

function getFile(myCallback) {
  let req = new XMLHttpRequest();
  req.open('GET', "mycar.html");
  req.onload = function() {
    if (req.status == 200) {
      myCallback(this.responseText);
    } else {
      myCallback("Error: " + req.status);
    }
  }
  req.send();
}

getFile(myDisplayer);

Promise

Promise是一种异步编程的解决方案,用于处理异步操作并返回结果,从语法上看它是一个构造函数,从功能上看它是用来封装一个异步操作并可以获取它成功/失败的结果值

Promise 是一个 JavaScript 的内置对象,初始化时,这个函数在构造之后会直接被异步运行,所以称之为起始函数,起始函数包含两个参数: resolverejectresolvereject 都是函数,其中调用resolve代表一切正常,是成功时的回调; reject 是出现异常是调用的

Promise 对象有三种状态:

  • pending(进行中):初始状态,既不是成功,也不是失败状态
  • resolved(又称fufilled)(已成功):意味着操作成功完成
  • rejected(已失败):意味着操作失败

一个 promise 对象只能改变一次状态,成功或者失败后都会返回结果数据,Promise 的状态一旦改变,就不能再次改变

Proimse 拥有两个实例方法:then()catch()

Promise基本语法:

js
let myPromise = new Promise(function(myResolve, myReject) {
// "Producing Code"(可能需要一些时间)

  myResolve(); // 成功时
  myReject();  // 出错时
});

// "Consuming Code" (必须等待一个兑现的承诺)
myPromise.then(  //Promise.then() 有两个参数,一个是成功时的回调,另一个是失败时的回调
  function(value) { /* 成功时的代码 */ },
  function(error) { /* 出错时的代码 */ }
);

当执行代码获得结果时,成功时调用:myResolve(result value);出错时调用:myReject(error object)

使用Promise改进的等待超时

js
let myPromise = new Promise(function(myResolve, myReject) {
  setTimeout(function() { myResolve("I love You !!"); }, 3000);
});

myPromise.then(function(value) {
  document.getElementById("demo").innerHTML = value;
});

Async/Await

asyncawaitES8引入的新语法,用来简化Promise异步操作

async 是 “异步”的简写,await 可以认为是async await的简写

async 用来声明一个function是异步的,await 用来等待一个异步方法执行完成

有个规定:await 只能出现在 async 函数中,await 关键字要写在 async 关键字函数的内部,写在外面会报错

async 使函数返回 Promise;await 使函数等待 Promiseawait 等待的是右侧表达式的返回值)

函数前的关键字 async 使函数返回 promiseasync声明的函数返回结果是一个Promise对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象),形如:Promise {<fulfilled>: 具体的return值}

async 表示函数内部有异步操作

js
async function myFunction() {
  return "Hello";
}
// 上述内容等价于
async function myFunction() {
  return Promise.resolve("Hello");
}
js
function myDisplayer(some) {
  document.getElementById("demo").innerHTML = some;
}

async function myFunction() {return "Hello";}

myFunction().then(
  function(value) {myDisplayer(value);},
  function(error) {myDisplayer(error);}
);

我们可以通过then() 链来处理这个Promise对象:

js
async function test(){
	return 'hello async';
}
test().then((val) => {
	console.log(val);  // hello async 
})

函数前的关键字 await 使函数等待 promise

语法:let value = await promise;

await 关键字只能在 async 函数中使用

如果 await 等到的不是一个 Promise 对象,那么 await 表达式的运算结果就是它等到的东西,比如返回的是字符串,那么运算结果就是字符串 如果 await 等到的是一个Promise对象,await 就开始忙起来,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为await表达式的运算结果。 async 调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 中,而 await 会等待 这个 Promise 完成,并将其 resolve 的结果返回出来

等待超时

js
async function myDisplay() {
  let myPromise = new Promise(function(myResolve, myReject) {
    setTimeout(function() { myResolve("I love You !!"); }, 3000);
  });
  document.getElementById("demo").innerHTML = await myPromise;
}

myDisplay();

简单的async/await使用例子:用 setTimeout 来进行一次模拟异步操作

js
function getData(n) {
	return new Promise(resolve => {
		setTimeout(() => resolve(n + 200), n)
	})
}

function step1(n) {
	console.log(`step1 值为 ${n}`)
	return getData(n)
}

function step2(m, n) {
	console.log(`step2 值为 ${m} + ${n}`)
	return getData(m + n)
}

function step3(k, m, n) {
	console.log(`step3 值为 ${k} + ${m} + ${n}`)
	return getData(k + m + n)
}

async function getDataByAwait() {
	const time1 = 300
	console.time('用时')
	const time2 = await step1(time1)
	const time3 = await step2(time1, time2)
	const result = await step3(time1, time2, time3)
	console.log('最终结果是:', result)
	console.timeEnd('用时')
}

getDataByAwait()

使用 async/await 时,Promise 有可能返回rejected的状态

我们在使用 async/await 的时候,由于 Promise 运行结果可能是 rejected,所以我们最好把 await 命令放在try catch代码块中

js
async getData = () => {
	try {
		await step1(200)
	} catch(err) {
		console.log(err)
	}
}

// 另一种写法
async getData = () => {
	await step1(200).catch(err = > console.log(err))
}

Released under the MIT License.