本案例我将介绍回调函数的基本概念与应用,并一步一步的演示回调地狱是怎么产生的,最后是怎么解决回调地狱问题的es6
首先我这里有个需求:打印变量str
编程
function getDate(){
function processData(){
var str="hello world";
}
}
复制代码
正常的思惟可能会这样打印promise
function getDate(){
function processData(){
var str="hello world";
return str;
}
processData();
}
var res=getDate();
console.log(res);//undefined
复制代码
可是实际的结果是undefined,为何呢?浏览器
这里涉及到一个概念:异步bash
什么是异步?闭包
简单理解就是:两件事情同时进行,如边听歌边写做业异步
既然是两件事情同时在进行,那么就必然会出现:异步编程
好了,上面的例子为何不能打印出str
的?函数
getDate()
与processData()
是两个不一样函数,只是嵌套在一块儿而已,暂且无论他们结束时间是否一致,可是确定的是console.log(res)
打印的是getDate()
的返回值,可是getDate()
并无返回值,因此结果是undefined学习
那怎么解决,好办
给getData()
一个返回值
function getDate(){
function processData(){
var str="hello world";
return str;
}
return processData();
}
var res=getDate();
console.log(res);//hello world
复制代码
到了这里,咱们确实是实现了打印str
的值,可是,这种方法真的是可行的吗?
固然不是
下面例子,个人需求是打印sum的值
function getDate(){
function processData(){
var sum=0;
for(var i=0;i<4;i++){
sum++;
}
}
return processData();
}
var res=getDate();
console.log(res);//undefined
复制代码
为何到了这个例子就不起做用了呢?这就要回归"异步"函数的本质上:
上一个例子是异步函数结果不一样打印不出str
,而这个例子就是异步函数结束时间不一样致使打印不出sum
为何呢?根据结果能够知道在执行下面代码时
var res=getDate();
console.log(res);
复制代码
processData()
尚未执行完,天然return processData()
就不能返回有效的值
到此,是时候介绍咱们的主角了
像上面两个例子的需求在实际的开发中仍是会常常遇到的,这时,就须要咱们去探索一种有效的解决方法,那就是:回调函数
回调函数是做为参数传给另外一个函数的函数
对于回调函数概念的解释就和闭包同样,并无惟一解释,可是在实践中总结出来的最接近咱们理解的说法是有的,简单的来讲,回调函数除了是一个函数外,仍是别的函数的一个参数
回调函数的基本模型没有固定的写法,借用上面简单的例子来写一个启发模型:打印sum
function getDate(callback){
function processData(){
var sum=0;
for(var i=0;i<4;i++){
sum++;
}
callback(sum);//调用外部的函数,把里面的值带出去,实现外部访问
}
processData();
}
getDate((data)=>{
console.log(data);
});
复制代码
通常状况下,把函数做为参数的目的就是为了获取函数内部的异步操做结果
我在同一个目录下分别新建了三个文件:
aa.txt
bb.txt
cc.txt
test.js
其中,aa.txt,bb.txt,cc.txt的内容分别为aaaa,bbbb,cccc
下面是test.js
let path=require("path");
let fs=require('fs');
fs.readFile(path.join(__dirname,"./aa.txt"),"utf8",function(err,data){
if(err) throw err
console.log(data)
})
复制代码
执行代码,获得aaaa
下面修改代码,使用回调函数来打印
let path=require("path");
let fs=require('fs');
function getFileByPath(fpath,callback){
fs.readFile(fpath,"utf8",function(err,data){
if(err) return callback(err.message)
callback(data)
})
}
strurl=path.join(__dirname,"./aa.txt");
getFileByPath(strurl,(data)=>{
console.log(data);
})
复制代码
执行代码,获得aaaa
因为上面打印err,data,都是共用同一个callback,为了代码的井井有条,对他们分别使用回调函数,下面修改代码
function getFileByPath(fpath,succb,errcb){
fs.readFile(fpath,'utf8',(err,data)=>{
if(err) return errcb(err.message)
succb(data)
})
}
getFileByPath(path.join(__dirname,'aa.txt'),(data)=>{
console.log(data)
},
(err)=>{
console.log(err)
})
复制代码
执行代码,获得aaaa
下面我要将aa.txt,bb.txt,cc.txt三个文件的内容依次读取出来,注意是"依次",下面修改代码
fs.readFile(path.join(__dirname,"aa.txt"),"utf8",function(err,data){
console.log(data)
})
fs.readFile(path.join(__dirname,"bb.txt"),"utf8",function(err,data){
console.log(data)
})
fs.readFile(path.join(__dirname,"cc.txt"),"utf8",function(err,data){
console.log(data)
})
复制代码
重复执行代码,观察结果
从结果能够看出,在重复执行屡次代码,发现获得的结果中有出现读取顺序并非咱们预想的结果,这个问题也是由异步引发的,是由于三个读取文件的函数结束时间不必定相同
所以人们采用了这种方案来解决这个问题
fs.readFile(path.join(__dirname,"aa.txt"),"utf8",function(err,data){
console.log(data)
fs.readFile(path.join(__dirname,"bb.txt"),"utf8",function(err,data){
console.log(data)
fs.readFile(path.join(__dirname,"cc.txt"),"utf8",function(err,data){
console.log(data)
})
})
})
复制代码
这个结构就保证了函数执行的顺序,可是若是咱们要读取不少的文件,那不是要这样一直嵌套下去?
这就是回调地域问题
为了解决回调地狱的写法带来的缺点,能够采用Promise解决方案
下面用Promise来读取单个文件
let p=new Promise(function(res,rej){
fs.readFile(path.join(__dirname,'aa.txt'),'utf8',function(err,data){
if(err) rej(err.message)
else res(data)
})
})
p.then((data)=>{
console.log(data)//aaaa
})
复制代码
下面将aa.txt,bb.txt,cc.txt三个文件的内容依次读取出来
function fn(fpath){
return new Promise(function(res,rej){
fs.readFile(fpath,"utf8",function(err,data){
res(data)
})
})
}
fn(path.join(__dirname,"aa.txt"))
.then((data)=>{
console.log(data)
return fn(path.join(__dirname,"bb.txt"))
})
.then((data)=>{
console.log(data)
return fn(path.join(__dirname,"cc.txt"))
})
.then((data)=>{
console.log(data)
})
复制代码
特别声明
如下内容参考或引用自:
Promise 是异步编程的一种解决方案,所谓Promise
,简单说就是一个容器,里面保存着某个将来才会结束的事件的结果,这个事件一般是一个异步操做,从语法上说,Promise 是一个对象,而这个对象是一个构造方法,从它能够获取异步操做的消息。
// 下面的代码能够直接运行在浏览器的控制台中(Chrome浏览器)
> typeof Promise
"function" // 能够看出这是一个构造函数
> Promise
function Promise() { [native code] } // ES6的原生支持
复制代码
(1)对象的状态不受外界影响。Promise
对象表明一个异步操做,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操做的结果,能够决定当前是哪种状态,任何其余操做都没法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其余手段没法改变。
(2)一旦状态改变,就不会再变,任什么时候候均可以获得这个结果。Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种状况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。若是改变已经发生了,你再对Promise
对象添加回调函数,也会当即获得这个结果。这与事件(Event)彻底不一样,事件的特色是,若是你错过了它,再去监听,是得不到结果的。
注意,为了行文方便,本章后面的resolved
统一只指fulfilled
状态,不包含rejected
状态。有了Promise
对象,就能够将异步操做以"同步操做"的流程表达出来,避免了层层嵌套的回调函数。此外,Promise
对象提供统一的接口,使得控制异步操做更加容易。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操做成功 */){
resolve(value);
} else {
reject(error);
}
});
复制代码
Promise
构造函数接受一个函数做为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用本身部署。
resolve
函数的做用是,将Promise
对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操做成功时调用,并将异步操做的结果,做为参数传递出去;reject
函数的做用是,将Promise
对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操做失败时调用,并将异步操做报出的错误,做为参数传递出去。
Promise
实例生成之后,能够用then
方法分别指定resolved
状态和rejected
状态的回调函数。
注意:理解这些状态的变化,能够结合生命周期的概念进行理解
promise.then(function(value) {
// success
}, function(error) {
// failure
});
复制代码
then
方法能够接受两个回调函数做为参数。第一个回调函数是Promise
对象的状态变为resolved
时调用,第二个回调函数是Promise
对象的状态变为rejected
时调用。其中,第二个函数是可选的,不必定要提供。这两个函数都接受Promise
对象传出的值做为参数。
好了,我这里对于Promise相关知识的介绍就这么多,Promise更多的知识点能够到上面我给的那两个参考的连接进行学习
.then()
方法一旦建立一个Promise对象以后,咱们就可使用then方法来进行链式的调用,并且咱们能够把每一次的结果都返还给下一个then方法,而后在下一个then方法中对这个值进行处理。每个then方法中均可以再次新建立一个Promise对象,而后返还给下一个then方法处理。
下面,回头看看咱们为了解决回调地狱的是怎么应用Promise的?
let p=new Promise(function(res,rej){
fs.readFile(path.join(__dirname,'aa.txt'),'utf8',function(err,data){
if(err) rej(err.message)
else res(data)
})
})
p.then((data)=>{
console.log(data)//aaaa
})
复制代码
生成实例化对象P的时候,Promise构造方法中传进一个函数,函数有两个参数,分别是res
和rej
函数里面作一个异步动做,读取文件aa.txt
若是读取失败,rej(err.message)
将异步动做失败结果做为参数传递出去
不然res(data)
将异步动做成功结果做为参数传递出去
下面将aa.txt,bb.txt,cc.txt三个文件的内容依次读取出来
function fn(fpath){
return new Promise(function(res,rej){
fs.readFile(fpath,"utf8",function(err,data){
res(data)
})
})
}
fn(path.join(__dirname,"aa.txt"))
.then((data)=>{
console.log(data)
return fn(path.join(__dirname,"bb.txt"))
})
.then((data)=>{
console.log(data)
return fn(path.join(__dirname,"cc.txt"))
})
.then((data)=>{
console.log(data)
})
复制代码
fn()
,并传入参数执行异步动做,返回promise
实例,.then()
函数并传入回调函数,接收上一步传出来的值,并返回一个新的动做