回调地狱以及Promise解决方案

一.前言:

本案例我将介绍回调函数的基本概念与应用,并一步一步的演示回调地狱是怎么产生的,最后是怎么解决回调地狱问题的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

特别声明

如下内容参考或引用自:

知乎将来科技专栏-ES6 Promise

阮一峰-ECMAScript 6 入门

1.Promise的含义

Promise 是异步编程的一种解决方案,所谓Promise,简单说就是一个容器,里面保存着某个将来才会结束的事件的结果,这个事件一般是一个异步操做,从语法上说,Promise 是一个对象,而这个对象是一个构造方法,从它能够获取异步操做的消息。

// 下面的代码能够直接运行在浏览器的控制台中(Chrome浏览器)
> typeof Promise
"function" // 能够看出这是一个构造函数
> Promise
function Promise() { [native code] } // ES6的原生支持
复制代码

2.Promise的特色

(1)对象的状态不受外界影响。Promise对象表明一个异步操做,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操做的结果,能够决定当前是哪种状态,任何其余操做都没法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其余手段没法改变。

(2)一旦状态改变,就不会再变,任什么时候候均可以获得这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种状况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。若是改变已经发生了,你再对Promise对象添加回调函数,也会当即获得这个结果。这与事件(Event)彻底不一样,事件的特色是,若是你错过了它,再去监听,是得不到结果的。

注意,为了行文方便,本章后面的resolved统一只指fulfilled状态,不包含rejected状态。有了Promise对象,就能够将异步操做以"同步操做"的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操做更加容易。

3.Promise实例化

const promise = new Promise(function(resolve, reject) {
  // ... some code
  
  if (/* 异步操做成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
复制代码

Promise构造函数接受一个函数做为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 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更多的知识点能够到上面我给的那两个参考的连接进行学习

4.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构造方法中传进一个函数,函数有两个参数,分别是resrej

  • 函数里面作一个异步动做,读取文件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()函数并传入回调函数,接收上一步传出来的值,并返回一个新的动做
  • 以此类推直到最后一步
相关文章
相关标签/搜索