- 回调函数
- 回调地狱
- 如何解决回调地狱
- promise
- generator
- async 和 await
一、回调函数
定义:被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数。
(1)Callback风格的一般约定
确定的一点,任何一个程序流程都会有两种执行结果:正常返回计算出的最终结果、任何一处发生异常抛出错误。
基于这一点,我们用一种通用的方式来约定回调函数应该怎么去写
function callback(error, result) { if(error) { // 你处理错误的流程 console.error(error); } else { // 你处理正确结果的流程 console.info(result); } }
一般的回调函数为什么是两个参数?这是通过上述的原因约定俗成的结果。其中第一个参数代表子流程运行过程中发生的错误,而第二个参数指的是子流程正常运行的结果。一个接受该回调函数的函数可能长这样:
(2)可以多次调用的回调函数
既然回调函数是作为参数传入函数并在需要的时候调用的,那么并不限定回调函数只能用来处理错误和结果。来看一个多次调用回调函数的例子:
setInterval(function () { console.log('Hello'); }, 1000);
前面我们解释过回调函数的定义了,那么很容易可以看出来setInterval
这个函数的定义(callback: Function, period: number): void
。毋庸置疑,第一个参数其实就是一个回调函数,并且这个回调函数每秒钟会被调用一次。
利用这个方式,我们可以实现一个子流程,输出多次结果。是不是有一点像生成器(Generator)了?
(3)可以不止一个回调函数
回调函数只是参数,那么我们的函数就支持传入多个回调函数。比如:
function foo(handleSucceed, handleFailed) { if(isMistake) { return handleFailed(new Error('It`s a mistake')); } else { return handleSucceed('You got it!'); } } foo(function (result) { console.log(result); }, function (error) { console.error(error); });
通过传入两个不同的回调函数,来分别处理成功的结果和错误的结果,是不是感觉代码更加清晰容易理解了呢?你将要了解到的Promise
正是利用了这一点。
还有更多的对于回调函数的巧用方式,只要你记住回调函数只是一个函数类型的参数。
二、回调地狱
var fs = require('fs'); fs.readFile('./views/index.html', (err, data) => { if (err) { throw err } console.log(data.toString()); }) fs.readFile('./views/main.html', (err, data) => { if (err) { throw err } console.log(data.toString()); }) fs.readFile('./views/update.html', (err, data) => { if (err) { throw err } console.log(data.toString()); })
上面的代码中,包括三个异步读取文件的操作,因为是异步操作,文件输出的顺序是不确定的
如果想保证文件输出的顺序,就可以在前一个异步操作的回调函数中调用后一个异步操作,就会出现以下代码:
var fs = require('fs'); fs.readFile('./views/index.html', (err, data) => { if (err) { throw err } fs.readFile('./views/main.html', (err, data) => { if (err) { throw err } fs.readFile('./views/update.html', (err, data) => { if (err) { throw err } console.log(data.toString()); }) console.log(data.toString()); }) console.log(data.toString()); })
这种情况下便出现了回调地狱
假设业务开发中有4个接口,每个接口都依赖于前一个接口的返回,即request2依赖request1,request3依赖request2,这样就容易出现回调地狱的问题
三、如何解决回调地狱
3.1 promise
Promise是ES6标准新增的一个API
new Promise( function(resolve, reject) {...} /* executor */ );
executor
executor是带有 resolve
和 reject
两个参数的函数 。Promise构造函数执行时立即调用executor
函数, resolve
和 reject
两个函数作为参数传递给executor
(executor 函数在Promise构造函数返回所建promise实例对象前被调用)。resolve
和 reject
函数被调用时,分别将promise的状态改为fulfilled(完成)或rejected(失败)。executor 内部通常会执行一些异步操作,一旦异步操作执行完毕(可能成功/失败),要么调用resolve函数来将promise状态改成fulfilled,要么调用reject
函数将promise的状态改为rejected。如果在executor函数中抛出一个错误,那么该promise 状态为rejected。executor函数的返回值被忽略。
一般的使用方法如下:
const pms = new Promise((resolve, reject) => { // do sth. if(isMistake) { return resolve(new Error('It`s a mistake')); } else { return reject('You got it!'); } }); pms.then(result => { console.log(result); }).catch(error => { console.error(error); });
有没有发现?和上面一种对回调函数的使用方式出奇的像?
这里的resolve
和reject
正是两个回调函数,就如同前面一个例子里面的handleSucceed
和handleFailed
一样。而这两个回调函数的传入方式,从上一个例子的直接两个参数传入,变成了通过then
方法和catch
方法来进行传入
Promise
的then
方法和catch
方法本身也是返回一个Promise
对象的,因此可以直接进行链式调用,并且后一次的then
方法的回调函数的参数是前一次then
方法返回的结果。
实际上Promise上的实例promise是一个对象,不是一个函数。在声明的时候,Promise传递的参数函数会立即执行,因此Promise使用的正确姿势是在其外层再包裹一层函数。
let run = function() { return new Promise((resolve, reject) => { setTimeout(() => { let random = Math.random() if (random > 0.5) { resolve(`resolve:${random}`) } else { reject(`reject:${random}`) } }, 1000) }) } run()
run().then( function(value) { console.log(value) })
在一个then()方法调用异步处理成功的状态时,你既可以return一个确定的“值”,也可以再次返回一个Promise实例,当返回的是一个确切的值的时候,then会将这个确切的值传入一个默认的Promise实例,并且这个Promise实例会立即置为fulfilled状态,以供接下来的then方法里使用
let num = 0 let run = function() { return new Promise(resolve => { resolve(`${num}`)}) } run().then(val => { console.log(val) return val }) .then(val =>{ val++ console.log(val) return val }) .then(val =>{ val++ console.log(val) })
参考资料:https://blog.csdn.net/qq_42911663/article/details/86369813