回調地獄以及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()函數並傳入回調函數,接收上一步傳出來的值,並返回一個新的動做
  • 以此類推直到最後一步
相關文章
相關標籤/搜索