异步,同步,回调以及异步编程

本文深入探讨了同步与异步的概念,包括同步与异步的区别、阻塞与非阻塞的含义,并通过JavaScript的例子解析了单线程环境下如何实现异步编程。文中列举了回调函数、事件监听、发布/订阅、Promise对象和Generator函数以及async函数等多种异步编程方式,详细阐述了各自的优缺点和应用场景。
摘要由CSDN通过智能技术生成

1.同步和异步

同步和异步:描述一种行为方式,是否需要等结果

  • 同步:在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值的行为
  • 异步:调用在发出之后,这个调用就直接返回了,没有返回结果的行为

阻塞和非阻塞:描述一种等待的中间状态,所以和同异步没有必然联系

  • 阻塞:阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回的状态
  • 非阻塞:不能立刻得到结果之前,该调用不会阻塞当前线程的状态

举一个例子:原文
小张喜欢喝咖啡,同时养了好多狗;
出场人物:
小张:相当于我们的客户端进程
小狗大黑:阻塞处理的IO函数
小狗大黄:非阻塞处理的IO函数
小狗大白、大红:异步处理的IO函数

  • 小张派大黑去看咖啡煮好没,大黑等咖啡煮开了才回来(同步阻塞)
  • 小张派大黄去看咖啡煮好没,大黄看了一眼就回来了,过了一会,大黄再去看看咖啡煮好没(同步非阻塞)
  • 小张派大白和大红去看咖啡煮好没,大白和大红到了厨房后,一起在那等着;过了一会咖啡煮好了,大红大白一起回到客厅告诉小张(异步阻塞)
  • 小张派大白和大红去看咖啡煮好没,大白和大红到了厨房后,大白就回来告诉小张,大红已经到厨房啦;过了一会咖啡煮好了,大红回到客厅告诉小张(异步非阻塞)

那么采用何种方式,要看小张有没有养这个类型的狗(系统有没有相关API),在有的情况下,就看小张个人的需求了(业务需求)

2.回调

被当作参数的函数的call就是call back (回调)

array =[3,2,1,5,9,6,0,4]
array.sort(function(a,b){return a-b}) // [0, 1, 2, 3, 4, 5, 6, 9]
// function(a,b){return a-b}被当作函数的参数,且sort方法运行时本身进行了call操作, 即 回调
// 这个栗子中没有异步,所以回调和异步没有必然联系,回调是拿到异步结果的一种方式,
//当然回调也可以拿到同步的结果

setTimeout(fn, 1000) // fn被当作函数参数,且在1s后被call,即回调, fn被称作回调函数
// 该过程是异步的,所以说异步回调

3. JavaScript的异步编程

3.1 JavaScript是单线程语言

JavaScript 运行机制详解:再谈Event Loop
阮一峰的文章介绍了JS的单线程特点

JavaScript的任务运行机制:

  • 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)

  • 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

  • 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行

  • 主线程不断重复上面的第三步。

由此可见,如果JS没有异步编程,那么单线程碰到耗时的阻塞任务,直接卡死,用户体验极差

3.2 JS异步编程的实现方式

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise 对象
  • Generator函数
  • async 函数
3.2.1 回调函数

异步编程最基本的方法

// finder和processor都是异步操作
function finder(records, cb) {    
    setTimeout(function () {
        records.push(3, 4);
        cb(records);
    }, 1000);
}
function processor(records, cb) {  
    setTimeout(function () {
        records.push(5, 6);
        cb(records);
    }, 1000);
}

// 使用回调
finder([1, 2], function (records) {             // 第一个回调操作
    processor(records, function(records) {   // 第二个回调操作
             console.log(records);       // [1, 2, 3, 4, 5, 6]
    });
});  // 继续嵌套会走向回调地狱,代码可读性极差

// 通过引用另一个函数,将上面的嵌套回调写得更清楚(回调地狱的一种解决方案)
function onProcessorDone(records){
    console.log(records);      
}
function onFinderDone(records) {
    processor(records, onProcessorDone);
}
finder([1, 2], onFinderDone);      //  [1, 2, 3, 4, 5, 6]

优点: 简单、容易理解和部署
缺点: 不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数

3.2.2 事件监听

采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。在jQuery和其他DOM库中经常出现

// 构造监听器
var finder = {
    run: function (records) {
        var self = this;
        setTimeout(function () {
            records.push(3, 4);
           self.trigger('done', [records]);
        }, 1000);
    }
}
var processor = {
    run: function (records) {
        var self = this;
        setTimeout(function () {
            records.push(5, 6);
            self.trigger('done', [records]);   
        }, 1000);
    }
 }
// 构造具有监听器行为的事件(对象)
var eventable = {
    on: function(event, cb) {
        $(this).on(event, cb);
    },
    trigger: function (event, args) {
        $(this).trigger(event, args);
    }
}
// 关联事件和监听器
$.extend(finder, eventable);
$.extend(processor, eventable);
// 使用监听器
finder.on('done', function (event, records) {
  processor.run(records);
});
processor.on('done', function (event, records) {
    console.log(records);
});
finder.run([1,2]);

优点: 可绑定多个事件,每个事件可指定多个回调函数,而且可以”去耦合”(Decoupling),有利于实现模块化
缺点: 事件驱动型,程序运行流程会变得不清晰

3.2.4 发布/订阅

订阅发布模式定义了一种一对多的依赖关系,通过调度中心让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态

未完待续
3.2.5 Promise 对象
  • Promise 确定了函数形式的规范,即规范了回调,直接使用 promise.then(f1, f2),成功执行f1,失败执行f2
  • Promise 可以对结果进行多次处理,即链式操作,promise.then(f1, f2).then(f3, f4),f2和f4可以省略,promise.then(f1, f2)返回的实例是 promise2 不是 promise
// 创建Promise实例
function finder(records){
	return new Promise((resolve, reject) => {
        setTimeout(function () {
        	records.push(3, 4);
        	resolve(records);
		}, 500);
	});
}
function processor(records) {
	return new Promise((resolve, reject) => {
        setTimeout(function () {
        	records.push(5, 6);
        	resolve(records);
		}, 500);
	});
}
// 链式调用
finder([1,2])
    .then(processor)
    .then(function(records) {
            console.log(records);
    });

优点: 将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易

缺点: 无法取消Promise,一旦新建它就会立即执行,无法中途取消;如果不设置回调函数,Promise内部抛出的错误,不会反应到外部;当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

Promise的更详细教程参考:ES6 Promise 对象

3.2.6 Generator函数

未完待续

3.2.7 async 函数

未完待续

参考文献:

  1. Asynchronous JS: Callbacks, Listeners, Control Flow Libs and Promises
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值