(渣渣小怪獸翻譯,若是看翻譯不開心能夠看->原文)javascript
js是一門強大語言。有時候,你必須處理另外一個回調中的回調,而這個回調這是另外一個回調中的回調。java
人們形象的描述這種模式爲回調地獄。node
它有點像這樣:promise
firstFunction(args, function() {
secondFunction(args, function() {
thirdFunction(args, function() {
// And so on…
});
});
});
複製代碼
這是js中的回調。當你看到這樣的嵌套回調, 它可能令你的大腦難以置信,但我不認爲這是「地獄」.這個「地獄」能夠更好的管理,若是你知道怎麼處理它的話。異步
我假設當你在閱讀這篇文章的時候你知道回調的概念.若是你不知道, 請閱讀這篇文章, 在咱們繼續往下走以前這篇文章會介紹什麼是回調。在那裏,咱們討論回調是什麼以及爲何在JavaScript中使用它們。async
這是四個處理回調地獄的方案函數
在咱們拆分講解這個解決方案以前, 讓咱們一塊兒構造一個回調地獄. 爲何?由於它真的太抽象了,當咱們看到上面的例子: firstFunction, secondFunciton 和 thirdFunction.咱們讓構造一個真是的回調, 讓咱們的例子更具體。ui
讓咱們想象, 如今咱們要作一個漢堡.爲了製做一個漢堡, 咱們須要執行如下步驟來達到目的:spa
若是這些步驟都是同步的, 你將看到一個函數像下面這樣:翻譯
const makeBurger = () => {
const beef = getBeef();
const patty = cookBeef(beef);
const buns = getBuns();
const burger = putBeefBetweenBuns(buns, beef);
return burger;
};
const burger = makeBurger();
serve(burger);
複製代碼
然而, 在咱們的步驟中,咱們不能本身製做漢堡。咱們必須指導助手製做漢堡的步驟。在咱們指示助手以後,咱們必須等待助手完成步驟,而後纔開始下一步。
若是咱們想在js中等待一會再執行, 咱們就須要使用回調。爲了製做漢堡, 咱們不得不先獲取牛肉。咱們只能在獲取牛肉後再煮牛肉。
const makeBurger = () => {
getBeef(function(beef) {
// We can only cook beef after we get it.
});
};
複製代碼
爲了煮牛肉, 咱們須要傳遞牛肉進入cookBeef函數內.不然咱們將沒有東西煮。所以咱們不得不等待牛肉煮熟.
一旦牛肉煮好了,咱們就要獲取麪包
const makeBurger = () => {
getBeef(function(beef) {
cookBeef(beef, function(cookedBeef) {
getBuns(function(buns) {
// Put patty in bun
});
});
});
};
複製代碼
在咱們獲取麪包後,咱們須要放肉餅在兩個麪包之間。這就是漢堡造成的地方.
const makeBurger = () => {
getBeef(function(beef) {
cookBeef(beef, function(cookedBeef) {
getBuns(function(buns) {
putBeefBetweenBuns(buns, beef, function(burger) {
// Serve the burger
});
});
});
});
};
複製代碼
最終, 咱們能夠提供漢堡了。可是咱們不能從makeBurger中返回burger, 由於這是異步的。咱們須要用一個回調去接收漢堡.
const makeBurger = nextStep => {
getBeef(function (beef) {
cookBeef(beef, function (cookedBeef) {
getBuns(function (buns) {
putBeefBetweenBuns(buns, beef, function(burger) {
nextStep(burger)
})
})
})
})
}
// Make and serve the burger
makeBurger(function (burger) => {
serve(burger)
})
複製代碼
製做這個回調的例子頗有趣啊
這個makeBurger回調是簡單易於理解。咱們能夠讀懂。它只是有一些很差看。
若是你第一次讀到makeBurger這個函數, 你可能在想「爲何咱們須要這麼多回調才能製做漢堡呢?這沒有意義!」
在這種狀況下,您須要留下注釋來解釋您的代碼。
// Makes a burger
// makeBurger contains four steps:
// 1. Get beef
// 2. Cook the beef
// 3. Get buns for the burger
// 4. Put the cooked beef between the buns
// 5. Serve the burger (from the callback)
// We use callbacks here because each step is asynchronous.
// We have to wait for the helper to complete the one step
// before we can start the next step
const makeBurger = nextStep => {
getBeef(function(beef) {
cookBeef(beef, function(cookedBeef) {
getBuns(function(buns) {
putBeefBetweenBuns(buns, beef, function(burger) {
nextStep(burger);
});
});
});
});
};
複製代碼
如今,不會是在想「WTF」, 當你看到這個回調地獄, 你能夠理解爲何要用這個方式去寫它了。
咱們的回調地獄的例子已是一個很棒拆分的例子。讓我給你一步步的拆分代碼, 就會明白爲何我這樣說了。
拿getBeef, 咱們的第一個回調來講, 咱們爲了拿到牛肉不得不先去冰箱。廚房裏有兩個冰箱,咱們須要去右手邊的冰箱拿牛肉。
const getBeef = nextStep => {
const fridge = leftFright;
const beef = getBeefFromFridge(fridge);
nextStep(beef);
};
複製代碼
爲了烹飪牛肉,咱們須要把牛肉放進烤箱裏, 把烤箱開到200度, 而且等20分鐘
const cookBeef = (beef, nextStep) => {
const workInProgress = putBeefinOven(beef);
setTimeout(function() {
nextStep(workInProgress);
}, 1000 * 60 * 20);
};
複製代碼
如今想象一下, 若是你不得不寫每一個步驟在makeBurger ,那麼這個函數就會很是的龐大。
有關將回調拆分爲較小函數的具體示例,您能夠在個人回調文章中閱讀這一小部分。
我猜你應該知道什麼是Promise.若是你不知道, 請先閱讀這一篇文章
Promise能讓回調地獄更易於管理.而不是像上面的嵌套回調.你將看到像這樣:
const makeBurger = () => {
return getBeef()
.then(beef => cookBeef(beef))
.then(cookedBeef => getBuns(beef))
.then(bunsAndBeef => putBeefBetweenBuns(bunsAndBeef));
};
// Make and serve burger
makeBurger().then(burger => serve(burger));
複製代碼
若是你更傾向於單參數風格的promise, 你能把上面的例子轉換成這樣
const makeBurger = () => {
return getBeef()
.then(cookBeef)
.then(getBuns)
.then(putBeefBetweenBuns);
};
// Make and serve burger
makeBurger().then(serve);
複製代碼
更易讀和管理。
可是問題是, 如何把回調地獄轉換成Promise?
爲了轉成Promise, 咱們須要爲每一個回調先new一個Promise.當這個回調成功執行,那麼給這個Promise使用resolve返回。或當這個回調失敗或者拋出錯誤的時候,咱們就要使用reject。
const getBeefPromise = _ => {
const fridge = leftFright;
const beef = getBeefFromFridge(fridge);
return new Promise((resolve, reject) => {
if (beef) {
resolve(beef);
} else {
reject(new Error(「No more beef!」));
}
});
};
const cookBeefPromise = beef => {
const workInProgress = putBeefinOven(beef);
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve(workInProgress);
}, 1000 * 60 * 20);
});
};
複製代碼
在咱們的練習中,可能已經爲您編寫了回調函數。若是使用Node,則包含回調的每一個函數將具備相同的語法:
// The function that’s defined for you
const functionName = (arg1, arg2, callback) => {
// Do stuff here
callback(err, stuff);
};
// How you use the function
functionName(arg1, arg2, (err, stuff) => {
if (err) {
console.error(err);
}
// Do stuff
});
複製代碼
若是你的回調已經有了相同的語法, 你可使用像ES6 Promisify or Denodeify 把回調轉換成Promise.若是你使用Node v8.0或者之上, 你可使用util.promisify.
他們三個均可以使用。你能夠選擇任意一個庫來使用。不過,每種方法之間都有細微的差異。我建議你查看它的文檔,瞭解方法。
爲了使用異步函數, 你首先須要知道兩件事。
使用異步函數,您能夠編寫makeBurger,就像寫同步同樣!
const makeBurger = async () => {
const beef = await getBeef();
const cookedBeef = await cookBeef(beef);
const buns = await getBuns();
const burger = await putBeefBetweenBuns(cookedBeef, buns);
return burger;
};
// Make and serve burger
makeBurger().then(serve);
複製代碼
咱們能夠在這裏對makeBurger進行一項改進。 你能夠同時得到兩個getBuns和getBeef的助手。 這意味着您可使用Promise.all語法, 而後使用await。
const makeBurger = async () => {
const [beef, buns] = await Promise.all(getBeef, getBuns);
const cookedBeef = await cookBeef(beef);
const burger = await putBeefBetweenBuns(cookedBeef, buns);
return burger;
};
// Make and serve burger
makeBurger().then(serve);
複製代碼
(注意: 你能夠在Promise作到相同的效果, 可是它的語法並非很棒, 不像async/await那麼清晰。)
回調地獄並非像你想象中的那麼可怕.這裏有四種方式管理回調地獄。