Promise和Generator

代碼案例以及文章內(nèi)容均來自阮一峰的ECAMScript6入門:http://es6.ruanyifeng.com/#docs/promise

Promise對象

Promise 是異步編程的一個解決方案并鸵,不同于回調(diào)妒茬,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果。

Promise對象有兩個特點

  1. 對象的狀態(tài)不受外界影響。Promise對象代表一個異步操作,有三種狀態(tài):Pending,Resolved(已完成),Rejected(失敗)二打,只有異步操作的結(jié)果可以決定當(dāng)前的狀態(tài)狼牺,其他任何操作都無法改變
  2. 一旦狀態(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)
  1. 內(nèi)置執(zhí)行器:之前的操作都是xxx.next,而async函數(shù)自帶執(zhí)行器谈截,他的執(zhí)行與普通函數(shù)一模一樣
var result=asymcReadFile();
  1. 更好的語義
  2. 更廣的適用性:async函數(shù)的await命令后面竹捉,可以是Promise對象和原始類型的值
  3. 返回值是Promise

上述的代碼案例均來之阮一峰的博客(更多代碼案例):http://es6.ruanyifeng.com/#docs/promise

書籍資料來自ECAMStrict6 入門第二版

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末芜辕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子块差,更是在濱河造成了極大的恐慌侵续,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件憨闰,死亡現(xiàn)場離奇詭異状蜗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鹉动,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門轧坎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泽示,你說我怎么就攤上這事缸血。” “怎么了边琉?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵属百,是天一觀的道長记劝。 經(jīng)常有香客問我变姨,道長,這世上最難降的妖魔是什么厌丑? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任定欧,我火速辦了婚禮渔呵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘砍鸠。我一直安慰自己扩氢,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布爷辱。 她就那樣靜靜地躺著录豺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饭弓。 梳的紋絲不亂的頭發(fā)上双饥,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機(jī)與錄音弟断,去河邊找鬼咏花。 笑死,一個胖子當(dāng)著我的面吹牛阀趴,可吹牛的內(nèi)容都是我干的昏翰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼刘急,長吁一口氣:“原來是場噩夢啊……” “哼棚菊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起叔汁,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤窍株,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后攻柠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體球订,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年瑰钮,在試婚紗的時候發(fā)現(xiàn)自己被綠了冒滩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡浪谴,死狀恐怖开睡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情苟耻,我是刑警寧澤篇恒,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站凶杖,受9級特大地震影響胁艰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一腾么、第九天 我趴在偏房一處隱蔽的房頂上張望奈梳。 院中可真熱鬧,春花似錦解虱、人聲如沸攘须。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽于宙。三九已至,卻和暖如春悍汛,著一層夾襖步出監(jiān)牢的瞬間限煞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工员凝, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留署驻,地道東北人。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓健霹,卻偏偏與公主長得像旺上,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子糖埋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 異步編程對JavaScript語言太重要宣吱。Javascript語言的執(zhí)行環(huán)境是“單線程”的,如果沒有異步編程瞳别,根本...
    呼呼哥閱讀 7,311評論 5 22
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持征候,譯者再次奉上一點點福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠祟敛,并抽取幸運大...
    HetfieldJoe閱讀 6,377評論 9 19
  • 特別說明疤坝,為便于查閱,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS...
    殺破狼real閱讀 685評論 0 1
  • 簡單介紹下這幾個的關(guān)系為方便起見 用以下代碼為例簡單介紹下這幾個東西的關(guān)系馆铁, async 在函數(shù)聲明前使用asyn...
    _我和你一樣閱讀 21,224評論 1 24
  • Promiese 簡單說就是一個容器跑揉,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果,語法上說埠巨,Pr...
    雨飛飛雨閱讀 3,358評論 0 19