Javascript的特點是異步战惊,Javascript不能等待信柿,如果你實現(xiàn)某件需要等待的事情钠绍,你不能停在那里一直等待結(jié)果回來舆声,相反,底線是使用回調(diào)callback:你定義一個函數(shù)柳爽,這個函數(shù)只有等到結(jié)果可用時才能被調(diào)用媳握。
這種回調(diào)模型對于好的代碼組織是沒有問題的,但是也可以通過從原始回調(diào)切換到promise解決很多問題磷脯,將promise看成是一個標準的數(shù)據(jù)容器蛾找,這樣會簡化你的代碼組織,可以成為基于promise的架構(gòu)赵誓。
什么是Promise?
一個promise是一個帶有".then()"方法的對象打毛,其代表的是一個操作的結(jié)果可能還沒有或不知道,無論誰訪問這個對象俩功,都能夠使用".then()"方法加入回調(diào)等待操作出現(xiàn)成功結(jié)果或失敗時的提醒通知幻枉。
那么為什么這樣做好處優(yōu)于回調(diào)呢?標準的回調(diào)模式在我們處理請求時需要同時提供回調(diào)函數(shù):
request(url, function(error, response) {
// handle success or error.
});
doSomethingElse();
很不幸绑雄,這段代碼意味著這個request函數(shù)并不知道它自己什么時候能夠完成展辞,當然也沒有必要,我們最終通過回調(diào)傳遞結(jié)果万牺。這會導致多個回調(diào)形成了嵌套回調(diào),或者稱為回調(diào)陷阱洽腺。
queryTheDatabase(query, function(error, result) {
request(url, function(error, response) {
doSomethingElse(response, function(error, result) {
doAnotherThing(result, function(error, result) {
request(anotherUrl, function(error, response) {
...
});
});
});
});
});
Promise能夠解決這種問題脚粟,允許低層代碼創(chuàng)建一個request然后返回一個對象,其代表著未完成的操作蘸朋,讓調(diào)用者去決定應該加入什么回調(diào)核无。
Promise是什么?
promise是一個異步編程的抽象藕坯,它是一個返回值或拋出exception的代理對象团南,一般promise對象都有一個then方法,這個then方法是我們?nèi)绾潍@得返回值(成功實現(xiàn)承諾的結(jié)果值炼彪,稱為fulfillment)或拋出exception(拒絕承諾的理由吐根,稱為rejection),then是用兩個可選的回調(diào)作為參數(shù)辐马,我們可以稱為onFulfilled和OnRejected:
var promise = doSomethingAync()
promise.then(onFulfilled, onRejected)
當這個promise被解決了拷橘,也就是異步過程完成后,onFulfilled和OnRejected中任何一個將被調(diào)用,
因此冗疮,一個promise有下面三個不同狀態(tài):
pending待承諾 - promise初始狀態(tài)
fulfilled實現(xiàn)承諾 - 一個承諾成功實現(xiàn)狀態(tài)
rejected拒絕承諾 - 一個承諾失敗的狀態(tài)
以讀取文件為案例萄唇,下面是使用回調(diào)實現(xiàn)讀取文件后應該做什么事情(輸出打印):
readFile(function (err, data) {
if (err) return console.error(err)
console.log(data)
})
如果我們的readFile函數(shù)返回一個promise,那么我們可以如下實現(xiàn)同樣的邏輯(輸出打印):
var promise = readFile()
promise.then(console.log, console.error)
這里我們有了一個值promise代表的是異步操作术幔,我們能夠一直傳遞這個值promise另萤,任何人訪問這個值都能夠使用then來消費使用它,無論這個值代表的異步操作是否完成或沒有完成诅挑,我們也能保證異步的結(jié)果不會改變四敞,因為這個promise代表的異步操作只會執(zhí)行一次,狀態(tài)是要么fulfilled要么是rejected揍障。
理解Promise
Promise可能是不同于日常直覺目养,為了理解它,一些重要原理必須記牢: .then()總是返回一個新的promise.毒嫡,如下面代碼:
var promise = readFile()
var promise2 = promise.then(readAnotherFile, console.error)
這里then的參數(shù)readAnotherFile, console.error是代表異步操作成功后的動作onFulfilled或失敗后的動作OnRejected癌蚁,也就是說,讀取文件成功后執(zhí)行readAnotherFile函數(shù)兜畸,否則失敗打印記錄錯誤努释。這種實現(xiàn)是兩個中只有一種可能。
我們再看下面上述代碼如下:
var promise = readFile()
var promise2 = promise.then(function (data) {
return readAnotherFile() // 如果readFile成功咬摇,執(zhí)行readAnotherFile
}, function (err) {
console.error(err) // 如果readFile不成功伐蒂,記錄,但是還是執(zhí)行readAnotherFile
return readAnotherFile()
})
promise2.then(console.log, console.error) // readAnotherFile函數(shù)的執(zhí)行結(jié)果
因為then返回一個promise肛鹏,它意味著promise能夠被chain串行鏈條花逸邦,這樣能避免回調(diào)地獄:
readFile()
.then(readAnotherFile)
.then(doSomethingElse)
.then(...)
Promise法則有兩部分必須分離:
(1).then()總是返回一個新的promise,每次你調(diào)用它在扰,它不管回調(diào)做什么缕减,因為.then()在回調(diào)被調(diào)用之前已經(jīng)給了你一個承諾promise,回調(diào)的行為只影響承諾promise的實施芒珠,如果回調(diào)返回一個值桥狡,那么promise將使用那個值,如果這個值是一個promise皱卓,返回這個promise實施后的值給這個值裹芝,如果回調(diào)拋出錯誤,promise將拒絕錯誤娜汁。
(2)被.then()返回的promise是一個新的promise嫂易,它不同于那些.then()被調(diào)用的promise,promise長長的鏈條有時會好些隱藏這個事實存炮,不管如何炬搭,每次.then()調(diào)用都會產(chǎn)生一個新的promise蜈漓,這里必須注意的是你真正需要考慮的是你最后調(diào)用.then()可能代表失敗,那么如果你不捕獲這種失敗宫盔,那么容易導致你的錯誤exception消失融虽。
一些人認為.then()串聯(lián)鏈條調(diào)用很類似fluent風格,但是長長的promise鏈條會讓人迷惑灼芭,最后切分為一個個有意義的函數(shù):
function getTasks() {
return $http.get('http://example.com/api/v1/tasks')
.then(function(response) {
return response.data;
});
}
function getMyTasks() {
return getTasks()
.then(function(tasks) {
return filterTasks(tasks, {
owner: user.username
});
});
}
在這個例子中有额,兩個函數(shù)各自獲得一個promise,攜帶了一個回調(diào)函數(shù)彼绷。
有趣的Promise
同樣的promise能夠接受任何數(shù)目的回調(diào)巍佑,當一個Promise被解決實施后,其中所有回調(diào)函數(shù)都會被調(diào)用寄悯,此外萤衰,一個promise在被解決實施后,甚至可以接受一個新的回調(diào)猜旬,這些回調(diào)完成能以正常方式被調(diào)用脆栋,這就允許我們使用回調(diào)實現(xiàn)簡單形式的緩存:
var tasksPromise;
function getTasks() {
taskPromise = taskPromise || getTasksFromTheServer();
return taskPromise;
}
這個案例中,getTasks()函數(shù)可以被任意次數(shù)調(diào)用洒擦,它總是返回銅牙的promise椿争,其中函數(shù)getTasksFromTheServer()卻只是被調(diào)用一次。
Node.js error-first回調(diào)模式
在Node.js中使用Javascript Generators
Generator與Fiber比較
學習Javascript ES6幾個重要特性