代碼案例以及文章內(nèi)容均來自阮一峰的ECAMScript6入門:http://es6.ruanyifeng.com/#docs/promise
Promise對象
Promise 是異步編程的一個解決方案并鸵,不同于回調(diào)妒茬,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果。
Promise對象有兩個特點
- 對象的狀態(tài)不受外界影響。Promise對象代表一個異步操作,有三種狀態(tài):Pending,Resolved(已完成),Rejected(失敗)二打,只有異步操作的結(jié)果可以決定當(dāng)前的狀態(tài)狼牺,其他任何操作都無法改變
- 一旦狀態(tài)改變峰档,就不會在變,任何時候都可以得到這個結(jié)果撮胧,如果改變已經(jīng)發(fā)生了桨踪,你在對Promise對象添加回調(diào)函數(shù),也會立即得到這個結(jié)果
//定義一個Promise
var promise=new Promise(function(resolve,reject){
//some code
let value =200;
if(true){
resolve(value);
}else{
reject(200);
}
})
使用
promise.then(function(value){
console.log(value);
},function(error){
console.log('error');
})
異步加載圖片
function loadImageAsync(url){
return new Promise(function(resolve,reject){
var image=new Image();
image.onload=function(){
resolve(image);
};
image.onerror=function(){
reject(new Error('could not load image at '+url));
};
image.src=url;
})
}
Promise包裝了一個加載圖片的異步操作芹啥,成功澤調(diào)用resolve馒闷,失敗調(diào)用reject
這里對xmlHttpRequest進(jìn)行了封裝,針對http請求返回一個promise對象叁征,
var getJSON=function(url){
var promise=new Promise(function(resolve,reject){
var client=new XMLHttpRequest();
client.open('GET',url);
client.onreadystatechange=handler;
client.responseType='json';
client.setRequestHeader('Accept','application/json');
client.send();
function handler(){
if(this.readyState!==4){
return;
}
if(this.status===200){
resolve(this.response);
}else{
reject(new Error(this.statusText));
}
};
});
return promise;
};
getJSON('/post.json').then(function(json){
console.log('Content:'+json);
},function(error){
console.log('出差了',error)
})
Promise實例具有then方法和catch方法纳账,所以推薦寫法
getJSON('/posts.json').then(function(posts){
}).catch(function(error){
console.log('發(fā)生錯誤',error);
})
簡化后的寫法
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
注意點
如果Promise狀態(tài)已經(jīng)變成Resolved,在拋出錯誤無效
var promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
這里拋出錯誤不會被捕獲
- Promise.all 可以將多個Promise實例捺疼,包裝成一個新的Promise實例,只有等所有Promise實例的狀態(tài)都變成fulfilled疏虫,或者其中一個變成rejected,才會調(diào)用后面的回調(diào)函數(shù)
const databasePromise = connectDatabase();
const booksPromise = databasePromise
.then(findAllBooks);
const userPromise = databasePromise
.then(getCurrentUser);
Promise.all([
booksPromise,
userPromise
])
.then(([books, user]) => pickTopRecommentations(books, user));
- Promise.race 同樣是將多個Promise包裝成一個新的Promise實例啤呼,但是如果其中一個實例狀態(tài)先改變卧秘,就傳遞給p的回調(diào)函數(shù)
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p.then(response => console.log(response));
p.catch(error => console.log(error));
//如果5秒fetch沒有反應(yīng),則拋出超時
- Promise.resolve/reject 是將現(xiàn)有對象轉(zhuǎn)換為Promise
var p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
var p = Promise.reject('出錯了');
// 等同于
var p = new Promise((resolve, reject) => reject('出錯了'))
p.then(null, function (s) {
console.log(s)
});
- Promise.done/finally 兩者最大的區(qū)別是finally接受個普通的回調(diào)函數(shù)作為參數(shù)
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();
server.listen(0)
.then(function () {
// run test
})
.finally(server.stop);
- Promise.try 在實際開發(fā)中官扣,我們不知道函數(shù)f是同步還是異步函數(shù)翅敌,但是想用Promise去處理,因為這樣我們就可以用then來處理
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
這是上面的一個缺點惕蹄,如果f是同步蚯涮,被Promise包裝后變成異步執(zhí)行,如果通過try來包裝為所有操作提供統(tǒng)一的處理機(jī)制
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
Generator
Generator 函數(shù)是ES6提供的一種異步編程解決方案,語法行為與傳統(tǒng)函數(shù)完全不同卖陵,Generator函數(shù)是一個狀態(tài)機(jī)遭顶,封裝了多個內(nèi)部狀態(tài),執(zhí)行一個Generator函數(shù)會返回一個迭代器對象泪蔫,可以依次便利Generator函數(shù)內(nèi)部的每一個狀態(tài)
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
其中value為 yield 標(biāo)記的值棒旗,done 表示是否遍歷結(jié)束,yield類似一個暫停標(biāo)記撩荣,調(diào)用next方法會遍歷下一個內(nèi)部狀態(tài)
任意一個對象的Symbol.iterator方法铣揉,等于該對象的遍歷器生成函數(shù)饶深,調(diào)用該函數(shù)會返回該對象的一個遍歷對象,由于Generator函數(shù)就是遍歷器生成函數(shù)逛拱,因此可以把Generator賦值給對象的Symbol.iterator屬性粥喜,從而使得該對象具有Iterator接口
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
next方法的參數(shù)
yield句本身沒有返回值,或者總是返回underfined橘券,next方法可以帶一個參數(shù)额湘,該參數(shù)作為上一個yield的返回值
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
return 方法可以返回給定值,并且終結(jié)遍歷Generator函數(shù)
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
Generator函數(shù)調(diào)用另一個Generator函數(shù)
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
Generator應(yīng)用
異步操作的同步化表達(dá)旁舰,可以將異步操作寫在yield語句中锋华,等調(diào)用next方法后再往后執(zhí)行,這實際上等同于不需要寫回調(diào)
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI();
// 加載UI
loader.next()
// 卸載UI
loader.next()
ajax的異步操作
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response);
});
}
var it = main();
it.next();
控制流管理
Promise的寫法
Promise.resolve(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})
.done();
Generator的寫法
function* longRunningTask(value1) {
try {
var value2 = yield step1(value1);
var value3 = yield step2(value2);
var value4 = yield step3(value3);
var value5 = yield step4(value4);
// Do something with value4
} catch (e) {
// Handle any error from step1 through step4
}
}scheduler(longRunningTask(initialValue));
function scheduler(task) {
var taskObj = task.next(task.value);
// 如果Generator函數(shù)未結(jié)束箭窜,就繼續(xù)調(diào)用
if (!taskObj.done) {
task.value = taskObj.value
scheduler(task);
}
}
Async
async 是Generator函數(shù)的語法糖毯焕,是的異步操作變得更加方便
var fs = require('fs');
var readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) reject(error);
resolve(data);
});
});
};
var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
寫成asyc函數(shù)就變成
var asyncReadFile = async function () {
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
外觀上看上去只是將*號替換成async细燎,將yield 替換成await望伦,但是 async對Generator函數(shù)的有了很大的改進(jìn)
- 內(nèi)置執(zhí)行器:之前的操作都是xxx.next,而async函數(shù)自帶執(zhí)行器谈截,他的執(zhí)行與普通函數(shù)一模一樣
var result=asymcReadFile();
- 更好的語義
- 更廣的適用性:async函數(shù)的await命令后面竹捉,可以是Promise對象和原始類型的值
- 返回值是Promise
上述的代碼案例均來之阮一峰的博客(更多代碼案例):http://es6.ruanyifeng.com/#docs/promise
書籍資料來自ECAMStrict6 入門第二版