本案例我將介紹回調函數的基本概念與應用,並一步一步的演示回調地獄是怎麼產生的,最後是怎麼解決回調地獄問題的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()
函數並傳入回調函數,接收上一步傳出來的值,並返回一個新的動做