溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

ES6中Promise和Generator的區(qū)別是什

發(fā)布時(shí)間:2021-08-03 15:34:26 來(lái)源:億速云 閱讀:385 作者:Leah 欄目:編程語(yǔ)言

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)ES6中Promise和Generator的區(qū)別是什,文章內(nèi)容豐富且以專(zhuān)業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

Promise

什么是Promise

Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案“回調(diào)函數(shù)和事件”更合理和更強(qiáng)大。

所謂Promise,簡(jiǎn)單說(shuō)就是一個(gè)容器,里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果。

從語(yǔ)法上說(shuō),Promise 是一個(gè)對(duì)象,從它可以獲取異步操作的消息。

Promise的特點(diǎn)

Promise有兩個(gè)特點(diǎn):

  1. 對(duì)象的狀態(tài)不受外界影響。

Promise對(duì)象代表一個(gè)異步操作,有三種狀態(tài):Pending(進(jìn)行中)、Resolved(已完成,又稱(chēng) Fulfilled)和Rejected(已失敗)。

只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無(wú)法改變這個(gè)狀態(tài)。

  1. 一旦狀態(tài)改變,就不會(huì)再變,任何時(shí)候都可以得到這個(gè)結(jié)果。

Promise對(duì)象的狀態(tài)改變,只有兩種可能:從Pending變?yōu)镽esolved和從Pending變?yōu)镽ejected。

這與事件(Event)完全不同,事件的特點(diǎn)是,如果你錯(cuò)過(guò)了它,再去監(jiān)聽(tīng),是得不到結(jié)果的。

Promise的優(yōu)點(diǎn)

Promise將異步操作以同步操作的流程表達(dá)出來(lái),避免了層層嵌套的回調(diào)函數(shù)。

Promise對(duì)象提供統(tǒng)一的接口,使得控制異步操作更加容易。

Promise的缺點(diǎn)

  1. 無(wú)法取消Promise,一旦新建它就會(huì)立即執(zhí)行,無(wú)法中途取消。

  2. 如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部拋出的錯(cuò)誤,不會(huì)反應(yīng)到外部。

  3. 當(dāng)處于Pending狀態(tài)時(shí),無(wú)法得知目前進(jìn)展到哪一個(gè)階段(剛剛開(kāi)始還是即將完成)。

Promise的用法

Promise對(duì)象是一個(gè)構(gòu)造函數(shù),用來(lái)生成Promise實(shí)例:

var promise = new Promise(function(resolve, reject) { 
// ... some code 
if (/* 異步操作成功 */){ 
resolve(value); 
} else { reject(error); } 
}
);

promise可以接then操作,then操作可以接兩個(gè)function參數(shù),第一個(gè)function的參數(shù)就是構(gòu)建Promise的時(shí)候resolve的value,第二個(gè)function的參數(shù)就是構(gòu)建Promise的reject的error。

promise.then(function(value) { 
// success 
}, function(error) { 
// failure }
);

我們看一個(gè)具體的例子:

function timeout(ms){
    return new Promise(((resolve, reject) => {
        setTimeout(resolve,ms,'done');
    }))
}

timeout(100).then(value => console.log(value));

Promise中調(diào)用了一個(gè)setTimeout方法,并會(huì)定時(shí)觸發(fā)resolve方法,并傳入?yún)?shù)done。

最后程序輸出done。

Promise的執(zhí)行順序

Promise一經(jīng)創(chuàng)建就會(huì)立馬執(zhí)行。但是Promise.then中的方法,則會(huì)等到一個(gè)調(diào)用周期過(guò)后再次調(diào)用,我們看下面的例子:

let promise = new Promise(((resolve, reject) => {
    console.log('Step1');
    resolve();
}));

promise.then(() => {
    console.log('Step3');
});

console.log('Step2');

輸出:
Step1
Step2
Step3

Promise.prototype.then()

then方法返回的是一個(gè)新的Promise實(shí)例(注意,不是原來(lái)那個(gè)Promise實(shí)例)。因此可以采用鏈?zhǔn)綄?xiě)法,即then方法后面再調(diào)用另一個(gè)then方法.

getJSON("/users.json").then(function(json){
    return json.name;
}).then(function(name){
    console.log(name);
});

上面的代碼使用then方法,依次指定了兩個(gè)回調(diào)函數(shù)。第一個(gè)回調(diào)函數(shù)完成以后,會(huì)將返回結(jié)果作為參數(shù),傳入第二個(gè)回調(diào)函數(shù)

Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)。

getJSON("/users.json").then(function(json){
    return json.name;
}).catch(function(error){
    console.log(error);
});

Promise 對(duì)象的錯(cuò)誤具有“冒泡”性質(zhì),會(huì)一直向后傳遞,直到被捕獲為止。也就是說(shuō),錯(cuò)誤總是會(huì)被下一個(gè)catch語(yǔ)句捕獲

getJSON("/users.json").then(function(json){
    return json.name;
}).then(function(name){
    console.log(name);
}).catch(function(error){
    //處理前面所有產(chǎn)生的錯(cuò)誤
    console.log(error);
});

Promise.all()

Promise.all方法用于將多個(gè)Promise實(shí)例,包裝成一個(gè)新的Promise實(shí)例

var p = Promise.all([p1,p2,p3]);
  1. 只有p1、p2、p3的狀態(tài)都變成fulfilled,p的狀態(tài)才會(huì)變成fulfilled,此時(shí)p1、p2、p3的返回值組成一個(gè)數(shù)組,傳遞給p的回調(diào)函數(shù)。

  2. 只要p1、p2、p3之中有一個(gè)被rejected,p的狀態(tài)就變成rejected,此時(shí)第一個(gè)被reject的實(shí)例的返回值,會(huì)傳遞給p的回調(diào)函數(shù)。

Promise.race()

Promise.race方法同樣是將多個(gè)Promise實(shí)例,包裝成一個(gè)新的Promise實(shí)例

var p = Promise.race([p1,p2,p3]);

只要p1、p2、p3之中有一個(gè)實(shí)例率先改變狀態(tài),p的狀態(tài)就跟著改變。那個(gè)率先改變的 Promise 實(shí)例的返回值,就傳遞給p的回調(diào)函數(shù).

Promise.resolve()

Promise.resolve()將現(xiàn)有對(duì)象轉(zhuǎn)為Promise對(duì)象.

Promise.resolve('js');
//等價(jià)于
new Promise(resolve => resolve('js'));

那么什么樣的對(duì)象能夠轉(zhuǎn)化成為Promise對(duì)象呢?

  1. 參數(shù)是一個(gè)Promise實(shí)例

  2. 參數(shù)是一個(gè)thenable對(duì)象

  3. 參數(shù)不是具有then方法的對(duì)象,或根本就不是對(duì)象

  4. 不帶有任何參數(shù)

Promise.reject()

Promise.reject(reason)方法也會(huì)返回一個(gè)新的 Promise 實(shí)例,該實(shí)例的狀態(tài)為rejected

var p = Promise.reject('error');
//等價(jià)于
var p = new Promise((resolve,reject) => reject('error'));

Promise.reject()方法的參數(shù),會(huì)原封不動(dòng)地作為reject的理由,變成后續(xù)方法的參數(shù)。這一點(diǎn)與Promise.resolve方法不一致

done()

Promise對(duì)象的回調(diào)鏈,不管以then方法或catch方法結(jié)尾,要是最后一個(gè)方法拋出錯(cuò)誤,都有可能無(wú)法捕捉到(因?yàn)镻romise內(nèi)部的錯(cuò)誤不會(huì)冒泡到全局)。因此,我們可以提供一個(gè)done方法,總是處于回調(diào)鏈的尾端,保證拋出任何可能出現(xiàn)的錯(cuò)誤

asyncFunc().then(f1).catch(f2).then(f3).done();

finally()

finally方法用于指定不管Promise對(duì)象最后狀態(tài)如何,都會(huì)執(zhí)行的操作。它與done方法的最大區(qū)別,它接受一個(gè)普通的回調(diào)函數(shù)作為參數(shù),該函數(shù)不管怎樣都必須執(zhí)行.

server.listen(1000).then(function(){
    //do something
}.finally(server.stop);

Generator

什么是Generator

Generator 函數(shù)是 ES6 提供的一種異步編程解決方案

從語(yǔ)法上,首先可以把它理解成,Generator函數(shù)是一個(gè)狀態(tài)機(jī),封裝了多個(gè)內(nèi)部狀態(tài)

執(zhí)行 Generator 函數(shù)會(huì)返回一個(gè)遍歷器對(duì)象.

形式上,Generator 函數(shù)是一個(gè)普通函數(shù),但是有兩個(gè)特征。一是,function關(guān)鍵字與函數(shù)名之間有一個(gè)星號(hào);二是,函數(shù)體內(nèi)部使用yield語(yǔ)句,定義不同的內(nèi)部狀態(tài)。

舉個(gè)例子:

function * helloWorldGenerator(){
    yield 'hello';
    yield 'world';
    return 'ending';
}

var gen = helloWorldGenerator();

輸出結(jié)果:

console.log(gen.next());
console.log(gen.next());
console.log(gen.next());

{ value: 'hello', done: false }
{ value: 'world', done: false }
{ value: 'ending', done: true }

yield

遍歷器對(duì)象的next方法的運(yùn)行邏輯如下:

(1)遇到y(tǒng)ield語(yǔ)句,就暫停執(zhí)行后面的操作,并將緊跟在yield后面的那個(gè)表達(dá)式的值,作為返回的對(duì)象的value屬性值。

(2)下一次調(diào)用next方法時(shí),再繼續(xù)往下執(zhí)行,直到遇到下一個(gè)yield語(yǔ)句。

(3)如果沒(méi)有再遇到新的yield語(yǔ)句,就一直運(yùn)行到函數(shù)結(jié)束,直到return語(yǔ)句為止,并將return語(yǔ)句后面的表達(dá)式的值,作為返回的對(duì)象的value屬性值。

(4)如果該函數(shù)沒(méi)有return語(yǔ)句,則返回的對(duì)象的value屬性值為undefined。

注意,yield句本身沒(méi)有返回值,或者說(shuō)總是返回undefined。

next方法可以帶一個(gè)參數(shù),該參數(shù)就會(huì)被當(dāng)作上一個(gè)yield語(yǔ)句的返回值。

function * f() {
    for( let i =0; true; i++){
        let reset = yield i;
        if(reset){
            i = -1;
        }
    }
}

let g = f();
console.log(g.next());
console.log(g.next());
console.log(g.next(true));

輸出結(jié)果:

{ value: 0, done: false }
{ value: 1, done: false }
{ value: 0, done: false }

可以看到最后的一步,我們使用next傳入的true替代了i的值,最后導(dǎo)致i= -1 + 1 = 0.

我們?cè)倏匆粋€(gè)例子:

function * f2(x){
    var y = 2 * ( yield ( x + 1));
    var z = yield (y / 3);
    return (x + y + z);
}

var r1= f2(5);
console.log(r1.next());
console.log(r1.next());
console.log(r1.next());

var r2= f2(5);
console.log(r2.next());
console.log(r2.next(12));
console.log(r2.next(13));

輸出結(jié)果:

{ value: 6, done: false }
{ value: NaN, done: false }
{ value: NaN, done: true }

{ value: 6, done: false }
{ value: 8, done: false }
{ value: 42, done: true }

如果next不傳值的話(huà),yield本身是沒(méi)有返回值的,所以我們會(huì)得到NaN。

但是如果next傳入特定的值,則該值會(huì)替換該yield,成為真正的返回值。

yield *

如果在 Generator 函數(shù)內(nèi)部,調(diào)用另一個(gè) Generator 函數(shù),默認(rèn)情況下是沒(méi)有效果的

function * a1(){
    yield 'a';
    yield 'b';
}

function * b1(){
    yield 'x';
    a1();
    yield 'y';
}

for(let v of b1()){
    console.log(v);
}

輸出結(jié)果:

x
y

可以看到,在b1中調(diào)用a1是沒(méi)有效果的。

將上面的例子修改一下:

function * a1(){
    yield 'a';
    yield 'b';
}

function * b1(){
    yield 'x';
    yield * a1();
    yield 'y';
}

for(let v of b1()){
    console.log(v);
}

輸出結(jié)果:

x
a
b
y

異步操作的同步化表達(dá)

Generator函數(shù)的暫停執(zhí)行的效果,意味著可以把異步操作寫(xiě)在yield語(yǔ)句里面,等到調(diào)用next方法時(shí)再往后執(zhí)行。這實(shí)際上等同于不需要寫(xiě)回調(diào)函數(shù)了,因?yàn)楫惒讲僮鞯暮罄m(xù)操作可以放在yield語(yǔ)句下面,反正要等到調(diào)用next方法時(shí)再執(zhí)行。所以,Generator函數(shù)的一個(gè)重要實(shí)際意義就是用來(lái)處理異步操作,改寫(xiě)回調(diào)函數(shù)。

我們看一個(gè)怎么通過(guò)Generator來(lái)獲取一個(gè)Ajax的結(jié)果。

function * ajaxCall(){
    let result = yield request("http://www.flydean.com");
    let resp = JSON.parse(result);
    console.log(resp.value);
}

function request(url){
    makeAjaxCall(url, function(response){
        it.next(response);
    });
}

var it = ajaxCall();
it.next();

我們使用一個(gè)yield來(lái)獲取異步執(zhí)行的結(jié)果。但是我們?nèi)绾螌⑦@個(gè)yield傳給result變量呢?要記住yield本身是沒(méi)有返回值的。

我們需要調(diào)用generator的next方法,將異步執(zhí)行的結(jié)果傳進(jìn)去。這就是我們?cè)趓equest方法中做的事情。

Generator 的異步應(yīng)用

什么是異步應(yīng)用呢?

所謂"異步",簡(jiǎn)單說(shuō)就是一個(gè)任務(wù)不是連續(xù)完成的,可以理解成該任務(wù)被人為分成兩段,先執(zhí)行第一段,然后轉(zhuǎn)而執(zhí)行其他任務(wù),等做好了準(zhǔn)備,再回過(guò)頭執(zhí)行第二段。

比如,有一個(gè)任務(wù)是讀取文件進(jìn)行處理,任務(wù)的第一段是向操作系統(tǒng)發(fā)出請(qǐng)求,要求讀取文件。然后,程序執(zhí)行其他任務(wù),等到操作系統(tǒng)返回文件,再接著執(zhí)行任務(wù)的第二段(處理文件)。這種不連續(xù)的執(zhí)行,就叫做異步。

相應(yīng)地,連續(xù)的執(zhí)行就叫做同步。由于是連續(xù)執(zhí)行,不能插入其他任務(wù),所以操作系統(tǒng)從硬盤(pán)讀取文件的這段時(shí)間,程序只能干等著。

ES6誕生以前,異步編程的方法,大概有下面四種。 回調(diào)函數(shù) 事件監(jiān)聽(tīng) 發(fā)布/訂閱 Promise 對(duì)象

回調(diào)函數(shù)

fs.readFile(fileA, 'utf-8', function(error,data){
    fs.readFile(fileB, 'utf-8', function(error,data){
}
})

如果依次讀取兩個(gè)以上的文件,就會(huì)出現(xiàn)多重嵌套。代碼不是縱向發(fā)展,而是橫向發(fā)展,很快就會(huì)亂成一團(tuán),無(wú)法管理。因?yàn)槎鄠€(gè)異步操作形成了強(qiáng)耦合,只要有一個(gè)操作需要修改,它的上層回調(diào)函數(shù)和下層回調(diào)函數(shù),可能都要跟著修改。這種情況就稱(chēng)為"回調(diào)函數(shù)地獄"(callback hell)。

Promise

Promise 對(duì)象就是為了解決這個(gè)問(wèn)題而提出的。它不是新的語(yǔ)法功能,而是一種新的寫(xiě)法,允許將回調(diào)函數(shù)的嵌套,改成鏈?zhǔn)秸{(diào)用。

let readFile = require('fs-readfile-promise');
readFile(fileA).then(function(){
    return readFile(fileB);
}).then(function(data){
    console.log(data);
})

Thunk函數(shù)和異步函數(shù)自動(dòng)執(zhí)行

在講Thunk函數(shù)之前,我們講一下函數(shù)的調(diào)用有兩種方式,一種是傳值調(diào)用,一種是傳名調(diào)用。

"傳值調(diào)用"(call by value),即在進(jìn)入函數(shù)體之前,就計(jì)算x + 5的值(等于6),再將這個(gè)值傳入函數(shù)f。C語(yǔ)言就采用這種策略。

“傳名調(diào)用”(call by name),即直接將表達(dá)式x + 5傳入函數(shù)體,只在用到它的時(shí)候求值。

編譯器的“傳名調(diào)用”實(shí)現(xiàn),往往是將參數(shù)放到一個(gè)臨時(shí)函數(shù)之中,再將這個(gè)臨時(shí)函數(shù)傳入函數(shù)體。這個(gè)臨時(shí)函數(shù)就叫做 Thunk 函數(shù)。

舉個(gè)例子:

function f(m){
    return m * 2;
}

f(x + 5);

上面的代碼等于:

var thunk = function () {
    return x + 5;
}
function f(thunk){
    return thunk() * 2;
}

在 JavaScript 語(yǔ)言中,Thunk函數(shù)替換的不是表達(dá)式,而是多參數(shù)函數(shù),將其替換成一個(gè)只接受回調(diào)函數(shù)作為參數(shù)的單參數(shù)函數(shù)。

怎么解釋呢?

比如nodejs中的:

fs.readFile(filename,[encoding],[callback(err,data)])

readFile接收3個(gè)參數(shù),其中encoding是可選的。我們就以?xún)蓚€(gè)參數(shù)為例。

一般來(lái)說(shuō),我們這樣調(diào)用:

fs.readFile(fileA,callback);

那么有沒(méi)有辦法將其改寫(xiě)成為單個(gè)參數(shù)的function的級(jí)聯(lián)調(diào)用呢?

var Thunk = function (fn){
    return function (...args){
        return functon (callback){
            return fn.call(this,...args, callback);
        }
    }
}

var readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback);

可以看到上面的Thunk將兩個(gè)參數(shù)的函數(shù)改寫(xiě)成為了單個(gè)參數(shù)函數(shù)的級(jí)聯(lián)方式?;蛘哒f(shuō)Thunk是接收一個(gè)callback并執(zhí)行方法的函數(shù)。

這樣改寫(xiě)有什么用呢?Thunk函數(shù)現(xiàn)在可以用于 Generator 函數(shù)的自動(dòng)流程管理。

之前在講Generator的時(shí)候,如果Generator中有多個(gè)yield的異步方法,那么我們需要在next方法中傳入這些異步方法的執(zhí)行結(jié)果。

手動(dòng)傳入異步執(zhí)行結(jié)果當(dāng)然是可以的。但是有沒(méi)有自動(dòng)執(zhí)行的辦法呢?

let fs = require('fs');
let thunkify = require('thunkify');
let readFileThunk = thunkify(fs.readFile);

let gen = function * (){
    let r1 = yield readFileThunk('/tmp/file1');
    console.log(r1.toString());

    let r2 = yield readFileThunk('/tmp/file2');
    console.log(r2.toString());
}

let g = gen();

function run(fn){
    let gen = fn();

    function next (err, data){
        let result = gen.next(data);
        if(result.done) return;
        result.value(next);
    }
    next();
}

run(g);

gen.next返回的是一個(gè)對(duì)象,對(duì)象的value就是Thunk函數(shù),我們向Thunk函數(shù)再次傳入next callback,從而出發(fā)下一次的yield操作。

有了這個(gè)執(zhí)行器,執(zhí)行Generator函數(shù)方便多了。不管內(nèi)部有多少個(gè)異步操作,直接把 Generator 函數(shù)傳入run函數(shù)即可。當(dāng)然,前提是每一個(gè)異步操作,都要是Thunk函數(shù),也就是說(shuō),跟在yield命令后面的必須是Thunk函數(shù)。

上述就是小編為大家分享的ES6中Promise和Generator的區(qū)別是什了,如果剛好有類(lèi)似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI