javascript在进行资源请求时,会使用**异步**的方法。这种聪明的方法能够巧妙地规避请求资源带来的延迟。但不太友好的是:我们并不能够由代码上直接判断出哪个方法进行资源请求,哪个方法是异步的。 ## 同步与异步 我们平时接触的大多数的代码都是同步的,同步代码有特性就是按书写的顺序依次执行。书写在前的执行也在前,书写在后的执行也在后;异步代码同打破了同步这一常的执行思路。即:书写在前的,不见得会执行在前;书写在后的,也不见得执行在后。 ## 异步示例 比如我们刚刚请求的所有教师资源: ```typescript ngOnInit(): void { this.httpClient.get<[]>('assets/teacher-all.json') .subscribe(teachers => this.teachers = teachers); } ``` 上述代码是一个标准的异步请求,我们简单对上述代码打几个**点**: ```typescript ngOnInit(): void { console.log('1'); ❶ this.httpClient.get<[]>('assets/teacher-all.json') ❷ .subscribe(teachers => { console.log('2'); ❸ this.teachers = teachers; }); console.log('3'); ❹ } ``` 来到控制台查看执行顺序为❶❷❹❸: ![](https://img.kancloud.cn/82/c5/82c5e2b9670d6e6a32ea8f5412811a98_554x217.png) 虽然❸在书写顺序上在❹之前,但在执行的顺序却在❹之后。 从根本上讲发生上述原因是由于`❷httpClient.get`是个异步方法,当执行到此方法时javascript聪明地继续往下执行❹,而不是等待其请求完毕后执行❸。这在真实的环境中是非常有必要的,如果没有异步的存在,我们就必须**等待** ❷ 返回结果后再去执行 ❹。而**等待**时间具有不确定性,可能等0.1秒就返回了结果,也可以等10秒,或是根本就等不到结果。在异步的作用下,我们可以忽略这个等待的过程,而仅在请求成功返回数据后再去接着执行提前书写好的❸。 ## 回调 由于**异步**的存在,我们无法(实际上是有办法的)像使用同步方法一样来获取到资源请求的返回值: ```typescript ngOnInit(): void { console.log('1'); const teachers❶ = this.httpClient.get<[]>('assets/teacher-all.json') .subscribe(); console.log❷(JSON.stringify(teachers, (key, value)😀 => { if (['_subscriptions', 'destination'].indexOf(key) === -1) { return value; } })); console.log('3'); } ``` * 😀 请忽略此处的代码,当前仅作演示,不需要大家掌握 比如我们在上述代码中预使用同步的思想来获取所有的教师数据,但实际上获取的却是: ![](https://img.kancloud.cn/d5/83/d58387283ba97cd00218e686062617a6_1080x210.png) 那么若要获取这个**等待**一会才会出现的返回值,则需要使用同步的思想。 ### 数据与算法 刘宇轩的高分文章[从零起步,真正理解Javascript回调函数](https://segmentfault.com/a/1190000021942060)很好的对回调函数进行了总结,适合于新手阅读。该文章中提到函数是由数据与算法组成的,我们普通的函数是固化算法,通过改变数据的方法来改变最终的结果: ```js /** * 平方算法 * @param 变化是参数a * @return 返回的是a的平方 */ var test = function (a) { return a * a; }; test(2); test(3); ``` ![](https://img.kancloud.cn/78/2e/782e03e572be4d89aed6d0bdfbcfa979_294x129.png) 而带有回调函数的这种方法则是数据固定,改变是算法: ```js /** * 数据固定,根据传入的算法来决定返回值 * @param 变化是算法 * @return 返回算法对2的处理结果 */ var test = function (callback) { return callback(2); }; // 平方算法 test(a => a * a); // 立方算法 test(a => a * a * a); ``` ![](https://img.kancloud.cn/99/d5/99d512cd457735cd618dfc58cc6bfc78_416x132.png) 普通的方法可从方法体内直接使用传入的参数;特殊的方法由于接收到的参数类型为算法(函数),所以使用`xxx(参数1, 参数2, ...)`的方法来使用传入的xxx参数。 其实当某个方法支持传入算法时,该算法是否执行、什么时候执行、执行几次、带有什么参数执行,则完全由该方法说了算。 比如: ```js /** * 不执行传入算法 * @param callback * @return 123 */ var test = function (callback) { return 123; }; // 平方算法 test(a => a * a); // 立方算法 test(a => a * a * a); ``` callback被执行0次:无论什么算法传入test,均不会被执行,最终的结果都是123。 ```js /** * 执行2次传入的算法 * @param callback * @return 2与3结过算法运算后相乘的结果 */ var test = function (callback) { return callback(2) * callback(3); }; // 平方算法 test(a => a * a); ``` callback被执行2次。 ![](https://img.kancloud.cn/31/ad/31ad471078543100c45c42d66e4baf47_298x108.png) 我们当然也可以在方法中使用for循环的方法来使callback执行多次。a 所以:callback是否被执行,何时被执行,以什么样的参数被执行,执行多少次,全部取决于test方法是否在其方法体中调用callback,什么时候调用callback,调用时候传了什么样的参数,以及调用了多少次callback。 ## 异步与回调 异步与回调的结合,则很好的解决了**等待**一会再返回数据的问题。这是由于在方法调用传入的算法的过程中,可以将方法中的值通过传入算法参数的方式来传递给算法,比如: ```js var test = function (callback) { return callback(2❶); }; ``` * ❶ 2作为参数传递给了callback。 而当callback接收到2时,便可以随意利用该值了,当然也包括将其打印到控制台,或是将其值赋予当前组件的任意属性: ```js var subscribe = function (callback) { 发起资源请求 等待请求并向JS发送通知:当前请求为异步,请不要等待我执行完毕 if (请求成功) { callback(请求得到的数据❶); } else { 什么也不做 } }; subscribe(teachers => this.teachers = teachers❷); ``` * ❶ 将请求到的数据通过算法的参数传递出去 * ❷ 接收到数据后,将其赋予当前组件的teachers属性 学到这,相信本节开篇的❶❷❹❸执行顺序也就不难理解了: ```typescript ngOnInit(): void { console.log('1'); ❶ this.httpClient.get<[]>('assets/teacher-all.json') ❷ .subscribe(teachers => { console.log('2'); ❸ this.teachers = teachers; }); console.log('3'); ❹ } ``` >[info] 在实际的开发中,回调函数更多的被用于异步传值。 # 本节作业 请自定义一个test方法,当如下调用时,在打印台中打印31,即:`2 * 2 + 3 * 3 * 3`; ```js test(a => a * a, b => b * b * b, c => console.log(c)); ``` ![](https://img.kancloud.cn/37/94/3794f3ac3a5d08ec179f11d957fc1592_528x115.png) 异步与回调是个新的重要知识点,如果你尚无法独立完成本作业,请尝试重新学习本小节内容。 >[success] 简单的事情重复做你就是专家,重复的事情认真做你就是赢家! 再定义一个test方法,当如下调用时,仍在打印台中打印17,即:`2 * 2 * 2 + 3 * 3`; ```js test(2, 3, a => a * a, b => b * b * b, c => console.log(c)); ``` ![](https://img.kancloud.cn/40/33/4033b033439cd7cb73d230e598cf13e8_718x120.png) # 本节资源 | 名称 | 地址 | | ------------------------------------ | ------------------------------------------------------------ | | 从零起步,真正理解Javascript回调函数 | [https://segmentfault.com/a/1190000021942060](https://segmentfault.com/a/1190000021942060) |