<option id="mwy0y"><strong id="mwy0y"></strong></option>
  • <ul id="mwy0y"><sup id="mwy0y"></sup></ul>
  • <ul id="mwy0y"></ul>
  • <del id="mwy0y"><dfn id="mwy0y"></dfn></del><ul id="mwy0y"><sup id="mwy0y"></sup></ul>
  • <abbr id="mwy0y"></abbr>

    千鋒教育-做有情懷、有良心、有品質的職業教育機構

    400-811-9990
    手機站
    千鋒教育

    千鋒學習站 | 隨時隨地免費學

    千鋒教育

    掃一掃進入千鋒手機站

    領取全套視頻
    千鋒教育

    關注千鋒學習站小程序
    隨時隨地免費學習課程

    上海
    • 北京
    • 鄭州
    • 武漢
    • 成都
    • 西安
    • 沈陽
    • 廣州
    • 南京
    • 深圳
    • 大連
    • 青島
    • 杭州
    • 重慶
    當前位置:長沙千鋒IT培訓  >  技術要點  >  千鋒長沙前端培訓分享之一首歌帶你搞懂Promise

    千鋒長沙前端培訓分享之一首歌帶你搞懂Promise

    來源:千鋒教育
    發布人:千鋒長沙
    時間: 2021-12-28 17:25:58

           Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。(摘自<ECMAScript 6 入門>),但是對于Promise的創建和執行流程,其實有很多人初學時,容易走入誤區。今天就以一首歌來帶大家學習Promise:

    Promise創建

    Promise翻譯過來的意思就是"承諾",其實Promise的創建本質就是一個許下承諾的過程,想到這我的腦海中不由得浮現了一首經典老歌,海鳴威 《你的承諾》。

    歌中相知相戀的兩個人,奈何情深緣淺,終于還是分開,許下了“不為彼此難過, 過各自的生活"這樣的承諾。但是隨后歌曲又以"Oh baby 你答應我的我都記得 ,但是你卻忘了你的承諾 ,不是說好彼此都不再聯絡, 誰都別再犯錯",暗示承諾的狀態發生改變。

    由此不難想象以下兩種情況:

    遵守承諾

    v2-e668d7e31b7ad5596195817508151dd3_b

    違背承諾

    v2-0f7304ccfc7a7a8ec65ac01557965b34_b

    同樣,JS中的Promise的創建也是許下代碼承諾的一種方式,它默認也包含三種狀態:

    pending (進行中)

    fulfilled(已成功)

    rejected(已失敗)

    那么如何許下JS中的承諾呢:

    var p = new Promise(function(){

    // 回調函數

    })

    Promise中接收一個回調函數(簡單說就是一個容器),傳入某個未來才會結束的事件(通常是一個異步操作)的結果;此時打印,你會得到一個Promise實例(實例化對象),展開之后默認顯示:

    Promise {<pending>}

    [[Prototype]]: Promise,

    [[PromiseState]]: "pending",

    [[PromiseResult]]: undefined,

    此時我們會看到

    [[PromiseState]]用于存儲Promise實例(當前承諾)的狀態 ,默認顯示pending ;

    [[PromiseResult]]用于表示Promise實例中存儲的數據 , 默認顯示undefined;

    [[Prototype]]表示原型 以后再做講解

    但是有人就會問了,既然有三種狀態,而默認是pending(進行中)狀態,那么我們如何將當前Promise實例的狀態改為另外兩種:

    這就不得不提到Promise的第一特征了:

    對象的狀態不受外界影響。Promise對象代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。

    可以理解為,狀態并不是自行改變的,而是由異步(同步也可以)的結果決定了.。

    所以Promise在創建過程中,傳入的回調函數接收了改變狀態的兩個方法 ,resolve,reject作為形式參數傳入.

    var p = new Promise(function(resolve,reject){//形式參數

    // 回調函數

    })

    resolve() 將Promise實例的狀態 由pending(進行中) 改為 fulfilled(已成功)

    reject() 將Promise實例的狀態 由pending(進行中) 改為rejected(已失敗)

    而且resolve() reject() 方法在調用時 還可以接收一個參數,作為數據傳入,存儲到Promise實例中。

    同步代碼展示:

    var p = new Promise(function(resolve,reject){//形式參數

    // 回調函數

    resolve(111)

    })

    console.log(p)

    結果:

    Promise {<fulfilled>: 111}

    [[Prototype]]: Promise

    [[PromiseState]]: "fulfilled" // 已成功

    [[PromiseResult]]: 111 // 存儲數據

    var p = new Promise(function (resolve, reject) {//形式參數

    // 回調函數

    reject(222)

    })

    Promise {<rejected>: 111}

    [[Prototype]]: Promise

    [[PromiseState]]: "rejected" // 已失敗

    [[PromiseResult]]: 222 // 存儲數據

    Uncaught (in promise) 111 // 報錯 (可以不用理會 后面會被then() / catch() 方法捕獲)

    resolve() 和 reject() 方法,在同步代碼中調用,會立即改變Promise實例的狀態。

    異步代碼展示:

    var p = new Promise(function (resolve, reject) {//形式參數

    setTimeout(function () {

    resolve(111);

    console.log("line 24:", p);

    }, 2000)

    })

    console.log("line 27:", p);

    結果依次為:

    line 27: Promise {<pending>}

    line 24: Promise {<fulfilled>: 111}

    -------------------------------------兩次代碼請分開執行-------------------------------------------------

    var p = new Promise(function (resolve, reject) {//形式參數

    setTimeout(function () {

    reject(111);

    console.log("line 24:", p);

    }, 2000)

    })

    console.log("line 27:", p);

    結果依次為:

    line 27: Promise {<pending>}

    ...2s后

    line 24: Promise {<rejected>: 111}

    報錯: 2.html:60 Uncaught (in promise) 111 (可以不用理會 后面會被then() / catch() 方法捕獲)

    resolve() 和 reject() 方法,在異步代碼中調用:由于JS是單線程,會優先在主線程執行同步代碼,異步代碼會放到任務隊列中,所以在頁面加載時,,Promise實例為pending(進行中)狀態,等待主線程執行完畢,任務隊列通知主線程,異步任務可以執行了,該任務才會進入主線程執行。

    此時setTimeout中的回調函數被調用,執行了resolve() 或 reject() 方法, Promise實例會隨之改變狀態,并存儲傳入的數據。

    那么resolve() reject() 方法,可以重復調用么?

    這就要講到Promise的第二大特征:一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise對象的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型) 注意,為了行文方便,本章后面的resolved統一只指fulfilled狀態,不包含rejected狀態。

    由此可見Promise本質,就是一個狀態機,當該Promise對象創建出來之后,其狀態就是pending(進行中),然后通過程序來控制到底是執行已完成,還是執行已失敗。

    因為Promise處理的是異步任務,所以我們還得對Promise做監聽,當Promise的狀態發生變化時,我們要執行相應的函數(then catch finally)。

    Promise的動態方法

    所謂Promise動態方法,即將封裝的方法存儲在Promise的原型對象(prototype)上,供所有通過構造函數創建的實例化對象使用。

    Promise.prototype.then()

    Promise 實例具有then方法,它的作用是為 Promise 實例添加狀態改變時的回調函數,前面說過 Promise實例的狀態可以從pending(進行中) 變為 fulfilled(已成功)或rejected(已失敗),所以then方法中接收兩個回調函數(它們都是可選的)。

    p.then(resolveHandler,rejectHandler)

    resolveHandler 第一參數,用于指定Promise實例,由pending(進行中) 變為 fulfilled(已成功)時執行的回調函數。

    rejectHandler 第二參數,用于指定Promise實例,由pending(進行中) 變為 rejected(已失敗)時執行的回調函數。

    可以理解為 then()方法中的兩個回調函數,提前規定好了狀態改變時需要執行的內容,等待以后Promise實例的狀態之后再執行對應的回調函數 (本質還是回調函數,要用魔法打敗魔法)。

    注意:resolveHandler rejectHandler 中可以傳入一個形式參數 接收Promise實例中存儲數據。

    // 等待兩秒后 隨機一個0-1的數,如果 num>=0.5 就變為fulfilled,否則變為rejected

    var p = new Promise(function (resolve, reject) {//形式參數

    setTimeout(function () {

    var num = Math.random();

    if (num >= 0.5) {

    resolve(111)

    } else {

    reject(2222);

    }

    console.log("line 24:", p);

    }, 2000)

    })

    console.log("line 27:", p);

    p.then(function (arg) {

    console.log("line 50", arg);

    }, function (arg) {

    console.log("line 52", arg);

    })

    情況1: (num>=0.5)

    line 27: Promise {<pending>}

    ...2秒后

    line 24: Promise {<fulfilled>: 111}

    line 50 111

    情況2: (num<0.5)

    line 27: Promise {<pending>}

    ...2秒后

    2.html:81 line 24: Promise {<rejected>: 2222}

    2.html:89 line 52 2222

    解析:在異步代碼中調用: 由于JS是單線程,會優先在主線程執行同步代碼,異步代碼會放到任務隊列中,所以

    Promise創建完畢,Promise實例為pending(進行中)狀態;

    調用 then()方法,傳入兩個回調函數,提前規定好了狀態改變時需要執行的內容,等待以后Promise實例的狀態之后再執行對應的回調函數;

    等待主線程執行完畢,任務隊列通知主線程,異步任務可以執行了,該任務才會進入主線程執行。=> 隨機一個0-1的隨機數,根據結果改變Promise實例的狀態 ,存儲傳入的數據 => 執行then方法中對應的回調函數。

    還有一個有趣的地方:then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。因此可以采用鏈式寫法,即then方法后面再調用另一個then方法。

    var p = new Promise(function (resolve, reject) {//形式參數

    setTimeout(function () {

    resolve(1);

    }, 2000);

    })

    p.then(function (num) {

    console.log("line 64:", num);

    return 2;

    }).then(function (num) {

    console.log("line 67:", num);

    return 2;

    })

    結果:

    line 64: 1

    line 67: 2

    上面的代碼使用then方法,依次指定了兩個回調函數。第一個回調函數完成以后,會將返回結果作為參數,傳入第二個回調函數。當然這里有一個前提,那就是Promise實例的狀態為已成功且代碼執行過程中不出錯的情況下。萬一出錯的話,那我們就可以使用then()方法的第二個回調函數或者catch()方法捕獲錯誤。

    Promise.prototype.catch()

    catch()方法是.then(null, rejection)或.then(undefined, rejection)的別名,用于指定發生錯誤時的回調函數。

    function readText(url) {

    var p = new Promise(function (resolve, reject) {

    $.ajax({

    type: "get",

    url: url,

    success: function (text) {

    resolve(text);

    },

    error:function(err){

    reject(err);

    }

    })

    })

    return p; // {pending}

    }

    readText("../data/1.txt").then(res => {

    console.log("line 93", res)

    }).catch(err => {

    console.log(err);

    })

    上面代碼中,readText()方法返回一個 Promise 對象,如果該對象狀態變為resolved,則會調用then()方法指定的回調函數;如果異步操作拋出錯誤,狀態就會變為rejected,就會調用catch()方法指定的回調函數,處理這個錯誤。另外,then()方法指定的回調函數,如果運行中拋出錯誤,也會被catch()方法捕獲。

    then和catch的鏈式操作

    then()和catch()配合使用,實現的鏈式操作效果更佳。

    var p = new Promise(function (resolve, reject) {

    setTimeout(function () {

    resolve(1);

    }, 2000);

    })

    console.log("line 21", p);

    p.then(function (num) {

    console.log("line 23:", num);

    return 2;

    }).then(function (num) {

    console.log("line 25:", num);

    }).then(function (num) {

    console.log("line 28:", num);

    }).catch(function (err) {

    console.log(err);

    })

    結果:

    line 21 Promise {<pending>}

    ...等待2s后

    line 23: 1

    line 25: 2

    line 28: 3

    上面的代碼使用then catch方法進行鏈式調用,依次指定了三個then()的回調函數以及一個catch()的回調函數。第一個回調函數完成以后,會將返回結果作為參數,傳入第二個回調函數,同樣第二個回調函數完成后,會將返回結果作為參數,傳入第三個回調函數,以此類推。當然這里同樣也有一個前提,那就是Promise實例的狀態為已成功且代碼執行過程中不出錯的情況下。萬一出錯的話,那catch()方法剛好捕獲鏈式操作過程中出現的錯誤。

    如果then方法中回調函數的返回值也是一個Promise實例;

    function readText(url) {

    var p = new Promise(function (resolve, reject) {

    $.ajax({

    type: "get",

    url: url,

    success: function (text) {

    resolve(text);

    },

    error: function (err) {

    reject(err);

    }

    })

    })

    return p; // {pending}

    }

    // ajax恐怖回調的解決方式1:

    // T = T1 + T2 + T3 (時間)

    var str = "";

    readText("../data/1.txt").then(txt => {

    console.log("line 36", txt);

    str += txt;

    return readText("../data/2.txt");

    }).then(txt => {

    console.log("line 40", txt);

    str += txt;

    return readText("../data/3.txt");

    }).then(txt => {

    console.log("line 44", txt);

    str += txt;

    return str;

    }).then(txt => {

    console.log("line 48", txt);

    }).catch(err => {

    console.log("失敗:", err);

    })

    結果:

    line 36 11111

    line 40 22222

    line 44 33333

    line 48 111112222233333

    上面代碼中,第一個then方法指定的回調函數,返回的是另一個Promise對象。這時,第二個then方法指定的回調函數,就會等待這個新的Promise對象狀態發生變化。如果變為resolved,繼續鏈式調用后面then方法中指定的回調函數,如果狀態變為rejected,就會被catch方法捕獲。

    注意:Promise 實例的錯誤具有“冒泡”性質,會一直向后傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch語句捕獲。

    Promise的靜態方法

    所謂Promise靜態方法,即將封裝的方法存儲在構造函數Promise上,由構造函數自身調用。

    Promise.all

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

    const p = Promise.all([p1, p2, p3]);

    相較于then方法的鏈式操作,Promise.all()更注重并發,即依次按順序發送多個請求,等待所有的請求均有結果之后,對應請求順序將數據依次存放到數組中返回。

    模擬封裝(簡易版)

    Promise.myAll = function (list) {

    return new Promise(function (resolve, reject) { // 返回一個新的Promise實例

    var arr = [];

    for (let i = 0; i < list.length; i++) {

    let p = list[i];// 每一個Promise實例(如果傳入的數據非Promise實例 會通過Promise.resolve轉化)

    // 提前規定好每一個實例 成功和失敗時執行的內容 => 請求有結果之后會執行

    p.then(res => { //等待異步操作有結果之后 對應下標放到數組中

    arr[i] = res;

    if (arr.length === list.length) { // 所有的請求全都成功 => (也是一個異步操作)

    resolve(arr);

    }

    }).catch(err => { //只要有一個失敗 就走catch

    console.log(err);

    reject(err);

    })

    }

    })

    }

    const p = Promise.myAll([p1, p2, p3]);

    通過以上代碼可以發現: (摘自ECMAScript 6 入門)

    Promise.all()方法接受一個數組作為參數,p1、p2、p3都是 Promise 實例,如果不是,就會先調用下面講到的Promise.resolve方法,將參數轉為 Promise 實例,再進一步處理。另外,Promise.all()方法的參數可以不是數組,但必須具有 Iterator 接口,且返回的每個成員都是 Promise 實例。

    p的狀態由p1、p2、p3決定,分成兩種情況。

    (1)只有p1、p2、p3的狀態都變成fulfilled,p的狀態才會變成fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。

    (2)只要p1、p2、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。

    Promise.race

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

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

    相較于Promise.all方法, Promise.race方法更側重狀態改變的速度。

    模擬封裝(簡易版)

    Promise.myRace = function (list) {

    return new Promise(function (resolve, reject) { // 返回一個新的Promise實例

    for (let i = 0; i < list.length; i++) {

    let p = list[i];// 每一個Promise實例 (如果傳入的數據非Promise實例 會通過Promise.resolve轉化)

    // 提前規定好每一個實例 成功和失敗時執行的內容 => 請求有結果之后會執行

    p.then(res => { //等待異步操作有結果之后 對應下標放到數組中

    resolve(res);

    }).catch(err => {

    reject(err);

    })

    }

    })

    }

    const p = Promise.myRace([p1, p2, p3]);

    通過以上代碼可以發現: (摘自ECMAScript 6 入門)

    只要p1、p2、p3之中有一個實例率先改變狀態,p的狀態就跟著改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。

    Promise.allSettled

    Promise.allSettled()方法同樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。

    const p = Promise.allSettled([p1, p2, p3]);

    相較于Promise.all方法,Promise.allSettled跟注重于不管每一個Promise實例是成功還是失敗,都會將結果依次按請求順序放到數組中。

    模擬封裝(簡易版)

    Promise.myAllSettled = function (list) {

    return new Promise(function (resolve, reject) { // 返回一個新的Promise實例

    var arr = [];

    for (let i = 0; i < list.length; i++) {

    let p = list[i];// 每一個Promise實例 (如果傳入的數據非Promise實例 會通過Promise.resolve轉化)

    // 提前規定好每一個實例 成功和失敗時執行的內容 => 請求有結果之后會執行

    p.then(res => { //等待異步操作有結果之后 對應下標放到數組中

    var obj = { status: "fulfilled", value: txt };

    arr[i] = obj;

    if (arr.length === list.length) { // 請求全都成功 => arr(異步)

    resolve(arr);

    }

    }).catch(err => {

    var obj = { status: "rejected", reason: err };

    arr[i] = obj;

    if (arr.length === list.length) { // 請求全都成功 => arr(異步)

    resolve(arr);

    }

    })

    }

    })

    }

    const p = Promise.myAllSettled([p1, p2, p3])

    值得注意的地方時,在將數據放到數組的過程中,傳入的Promise實例的狀態不同,放置的結果稍有不同(數組中的每個成員是一個對象,對象的格式是固定的)。

    // 異步操作成功時

    {status: 'fulfilled', value: value}

    // 異步操作失敗時

    {status: 'rejected', reason: reason}

    成員對象的status屬性的值只可能是字符串fulfilled或字符串rejected,用來區分異步操作是成功還是失敗。如果是成功(fulfilled),對象會有value屬性,如果是失敗(rejected),會有reason屬性,對應兩種狀態時前面異步操作的返回值。

    總結

    有了Promise對象,就可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。此外,Promise對象提供統一的接口,使得控制異步操作更加容易。

    Promise也有一些缺點。首先,無法取消Promise,一旦新建它就會立即執行,無法中途取消。其次,如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。第三,當處于pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

    升級用法 async await

    ES2017 標準引入了 async 函數,使得異步操作變得更加方便。

    async 函數是什么?一句話,它就是 Generator 函數的語法糖。

    基本用法

    async function fn() {

    var str = "";

    var text = await readText("../data/1.txt"); // Promise實例

    console.log("line 29:",text);

    str += text;

    var text = await readText("../data/2.txt"); // Promise實例

    console.log("line 33:",text);

    str += text;

    var text = await readText("../data/3.txt"); // Promise實例

    console.log("line 37:",text);

    str += text;

    return str;

    }

    fn().then(res=>{

    console.log("line 44:",res)

    }).catch(err=>{

    console.log("line 46:",err)

    })

    結果:

    line 29: 11111

    line 33: 22222

    line 37: 33333

    line 44: 111112222233333

    async函數的返回值也是一個Promise實例,在async函數執行的過程中,一旦遇到await就會先返回pending(進行中)狀態的Promise實例,等待異步操作有結果之后,繼續執行await之后的語句,依次類推,語句全部執行完畢且無錯誤的情況下,則返回的Promise實例會變為已成功,否則會變為已失敗。

    聲明:本站稿件版權均屬千鋒教育所有,未經許可不得擅自轉載。

    猜你喜歡LIKE

    最新文章NEW

    相關推薦HOT

    更多>>

    快速通道 更多>>

    最新開班信息 更多>>

    網友熱搜 更多>>