Promise 真的懂了嗎?

(1) var

  • var在函數外定義,是全局變量,函數內可以使用
  • var在函數內定義,是局部變量,函數外不能使用
  • var 在其他代碼塊中定義,是全局變量 ----- 如 if 等
var x = 10;

( 
  function fun() {
     var y = 20;
     console.log(x)      // 10
  } 
)();

console.log(y)    // 報錯 y is not defined     ----- var在函數內部定義,是局部變量,函數外無法讀取



-------------------------------------

if (true) {
    var z = 300;
}
console.log(z)    // 300       // var在其他代碼塊中定義,是全局變量


(2) 對象es6

  • 表達式 - 可以作爲對象的屬性名和方法名
  • 變量名可以直接作爲對象屬性名
  • 對象中的方法可以簡寫
let ani = 'animal'
let nam = 'name'


let newAr = {
   ani // 變量名直接作爲對象的屬性名,等同於 ( ani: ani 即 ani: animal) 前者是字符串,後者ani是變量
   nam,
   [ani + nam] : 2000,   // 中括號中寫表達式,作爲對象的屬性名或方法名
   getAge() {            // 方法名簡寫,相當於 getAge: funciton() { console.log(this.animalname) }
      console.log(this.animalname)
   }
}


newAr.getAge();
console.log(newAr,'newAr')









Promise

Promise含義

  • promise是一種異步編程的解決方案
  • 比傳統的 回調函數和事件 更強大

Promise具體是什麼?

  • promise是一個容器,裏面保存着某個未來纔會結束的事件 (比如 異步操作的結果)
  • 語法上:promise是一個對象,可以從它獲取異步操作的消息
  • promise提供統一的api,所以各種異步操作都可以用同樣的方法進行處理

Promise對象的特點?

  • promise對象的狀態不受外界影響 ----- ( promise有三種狀態 )
    pending進行中,fulfilled已成功,rejected已失敗
    只有異步操作的結果可以決定當前是哪一種狀態,其他別的操作無法改變這個狀態
  • promise的狀態一旦改變,就不會再變,任何時候都能得到這個結果
    promise狀態改變只有兩種情況: pending狀態 變爲 fulfilled狀態pending狀態 變爲 rejected狀態
    只要這兩種情況發生,狀態就凝固了,不會再改變,稱爲 ( resolved ) 已定型
    例如:只要改變已經發生了,你再對promise對象添加回調函數,也會立即得到這個結果

Promise vs 事件?

Promise:只要改變已經發生了,你再對promise對象添加回調函數,也會立即得到這個結果
event----: 如果改變發生了,你再去監聽,得不到結果

Promise對象 的優缺點?

  • 優點
    promise對象,可以將 異步操作同步操作的流程 表達出來,避免層層嵌套
  • 缺點
    (1) promise一旦新建,就會立即執行,無法取消
    (2) 如果不設置回掉函數,promise內部拋出的錯誤就不會反應到外部
    (3) 處於pending狀態時,是不能知道目前進展到哪個階段的 ( 剛開始?,即將結束?)

Promise構造函數

es6規定,Promise對象 是一個構造函數,用來生成Promise實例

  • Promise構造函數,接受一個函數作爲參數,該函數有兩個參數分別是 resolve 和
    reject,它們是兩個函數
  • resolve 和 reject 由 javascript 引擎提供,不用自己部署
  • resolve函數
    resolve函數的作用是,將Promise對象的狀態,由pending=>變爲fulfilled,在異步操作成功時調用,並將異步操作的結果,作爲參數傳遞出去
  • reject函數
    reject函數的作用是,將Promise對象的狀態,由pending=>變爲rejected,在異步操作失敗時調用,並將異步操作報出的錯誤,作爲參數傳遞出去

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 異步操作成功 */){
    ... 異步操作
    resolve(value);  //  異步操作的結果作爲參數,傳遞出去
  } else {
    reject(error);
  }
});

  • .then 方法
    (1) promise實例生成以後,可以用.then方法,分別指定resolved狀態 和 rejected狀態的回調函數
    (2) .then方法可以接受 ( 兩個回調函數 ) 作爲參數,
    ===> 第一個回調函數,在promise對象的狀態變爲 resolved時調用 ------ (該回調函數在promise構造函數中定義,在實例化後,.then方法中調用 )
    ===> 第二個回調函數,在promise對象的狀態變爲 rejected 時調用 ------ (該回調函數在promise構造函數中定義,在實例化後,.then方法中調用 )
    (3) 第二個回調是可選的,可以不提供
    (4) 這兩個函數,都接受 promise對象 傳出的值 作爲參數

promise.then(function(value) {   

  // success
  // 該回調函數在promise對象狀態變爲resolved時調用,參數是promise對象 resolved時,傳出的值

}, function(error) {
  // failure
});

很重要的一個例子

//很重要的一個例子


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

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


上面代碼中,timeout方法返回一個Promise實例,表示一段時間以後纔會發生的結果。

過了指定的時間(ms參數)以後,Promise實例的狀態變爲resolved,就會觸發then方法綁定的回調函數。

promise新建後會立即執行

// promise新建後會立即執行



let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();  // 這裏沒有傳參,作用是去觸發.then中的回調函數

     // resovle()函數的作用,是把promise實例對象的狀態變爲resolved,  --------
     // 在異步操作成功時調用,並將異步操作的結果作爲參數傳遞出去

     // 而當promise實例對象的狀態變爲 resolved時,就會觸發 .then函數中的 回調函數   --------
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise                   // 注意執行順序
// Hi!
// resolved



上面代碼中,Promise 新建後立即執行,所以首先輸出的是Promise。

然後,then方法指定的回調函數,將在當前腳本所有同步任務執行完纔會執行,所以resolved最後輸出。

( 請求圖片 ) https://blog.csdn.net/h1534589653/article/details/77528367
( Image對象 ) https://blog.csdn.net/baihuaxiu123/article/details/53091105

異步加載圖片

  componentDidMount() {
        
        function loadImage(url) {
            return new Promise((resolve, reject) => {
                const image = new Image('400', '200')   // 生成image實例對象,寬高
                image.onload = () => {
                    resolve(image)               // 加載成功時候,返回image對象
                }
                image.onerror = () => {
                    reject( new Error(`could not load image at ${url}`))   // 加載失敗是報錯
                }
                image.src = url   // 請求的圖片地址
            })
        }


        loadImage('http://pic.7y7.com/201410/2014102458431393_600x0.jpg')
            .then(res => {
                console.log( res.src )   // 拿到image對象的src 屬性
                this.setState({
                    images: res
                },() => {console.log(this.state.images)})
            },  () => {
                    // rejected狀態下的回掉函數
            })
            
    } 

resolve函數,reject函數 ------------- (重要)

  • 如果調用resolve和reject函數時,帶有參數,那麼他們的參數會傳給回調函數
  • reject函數 的 參數, 通常是 Error對象的實例,表示拋出的錯誤
  • resolve函數的參數,除了正常值以外,還可能是另一個promise實例

const p1 = new Promise(function (resolve, reject) {
  // ...
});

const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);       // p2異步操作的結果返回 --> p1 異步操作
})


上面代碼中,p1和p2都是 Promise 的實例,

但是p2的resolve方法將p1作爲參數,即一個異步操作的結果是返回另一個異步操作。



----------------------------------------------------


注意: 上面例子中

p1的狀態決定了p2的狀態

(1) 如果 p1 的狀態是pending,那麼 p2 的回調函數就會等待 p1 的狀態改變
(2) 如果 p1 的狀態是 fulfilled或者rejected,那麼 p2 的回調函數將會立即執行

Promise嵌套 ---------------------------(重要)(重要)(重要)



componentDidMount() {
        let i = 0
        setInterval(() => {
            console.log(`經過了${++i}s`)
        },1000)

        const p1 = new Promise( (resolve,reject) => {
            setTimeout(() => {
                reject(new Error('fail'))
                console.log('3s')  // console.log語句仍然會執行,並且在reject()異步函數 前執行
            },3000)
        })
        const p2 = new Promise( (resolve,reject) => {
            setTimeout( () => {
                return resolve(p1)   // 一般都在這裏加return,這樣後面的代碼就不會執行,防止意外!!
                console.log('1s')
            }, 1000 )
        })

        p2.then(res => console.log(res))    // 並沒有執行
          .catch(error => console.log(error))

    // 注意: p2.then(res => console.log(....))並沒有執行,因爲p2的狀態變成了p1的狀態,是rejected
    // p2.then(res => console.log(res,'fulfilled'), res => console.log(res,'rejected'))
    // 實際執行的是上面的 第二個回調函數
}


解析:
(1) p1 是一個promise對象,3s後狀態變爲rejected
(2) p2 是一個promise對象,狀態在 1s 後改變,但是P2的resolvef方法返回的是p1,p1是promise對象

               導致p2的狀態由p1決定,即 p1的狀態傳遞給p2

(3) P2會等待P1的狀態改變爲 fulfilled或者reject,P1狀態改變後,P2的回調函數會立刻執行 ( --!!!重要!!!-- )

               ( 所以1s的時候,.then方法並沒有輸出內容 )
      (並且3s後,p2的狀態不是fulfilled,而是 rejeced,即是p1的狀態 )

(4) 又過了2s,p1的狀態變爲 rejected,導致觸發 .catch 回調函數


  • 一般來說,調用resolve或reject以後,Promise 的使命就完成了,後繼操作應該放到then方法裏面,而不應該直接寫在resolve或reject的後面。所以,最好在它們前面加上return語句,這樣就不會有意外。

Promise.prototype.then() 方法

promise實例有.then方法,是定義在原型對象 Promise.prototype 上的

  • then() 方法的作用是爲 promise實例添加狀態改變後的回調函數
  • then() 方法的參數是兩個回掉函數,第一個是 resolved 狀態的回調函數,第二個是rejected狀態的回調函數 ( 第二個參數可選,一般都不用,而用 catch()方法捕獲錯誤 )
  • then() 方法返回的是新的promise實例,因此可以採用鏈式寫法

Promise.prototype.then()方法的鏈式調用---------(重要)

採用鏈式的then,可以指定一組按照次序調用的回調函數。

  • 這時,前一個回調函數,有可能返回的還是一個Promise對象(即有異步操作),這時後一個回調函數,就會等待該Promise對象的狀態發生變化,纔會被調用。


componentDidMount() {
        let i = 0
        setInterval(() => {
            console.log(`經過了${++i}s`)
        },1000)
    
        const lian1 = new Promise( (resolve,reject) => {
            return setTimeout(() => {
                resolve('2s的promise的fulfilled狀態返回值')
            },2000)
        })
        lian1
        .then(res => console.log(res))
        .then( res => {
            return new Promise( (resolve,reject) => {
                return  setTimeout(() => {
                    return reject('3s的promise的rejected狀態返回值')
                },1000)
            })
        })
        .then(res => console.log(res,'reject'), res => console.log(res, 'reject'))
}

//  經過了1s
//  經過了2s
//  2s的promise的fulfilled狀態返回值
//  經過了3s
//  3s的promise的fulfilled狀態返回值 reject
// 經過了4s

Promise.prototype.catch()

Promise.prototype.catch() 是 .then(null, rejection) 的別名,用於指定發生錯誤時的回調函數

  • 如果promise實例對象的狀態變爲rejected,就會觸發 catch() 方法指定的回調函數
  • 如果 .then() 方法指定的回調函數在運行中拋出錯誤,也會被 catch() 方法捕獲
  • promise對象的錯誤具有冒泡性質,會一直向後傳遞,直到被捕獲爲止
    ( 也就是說錯誤總是會被下一個catch語句捕獲 )
  • 一般來說,不要在.then()方法中定義rejected狀態的回調函數,而總是使用 .catch()方法
  • 一般總是建議,promise對象後要跟 catch()方法,這樣可以處理 promise內部發生法的錯誤,catch() 方法返回的還是promise對象,因此後面還可以接着調用 then() 方法
  • catch() 方法中還能再拋錯誤,如果 catch()方法拋出錯誤後,後面沒有catch()方法,錯誤就不會被捕獲,也不會傳遞到外層。如果catch()方法拋出錯誤後,後面有then()方法,會照常執行,後面有catch()方法,錯誤還會被再一次捕獲

p.then((val) => console.log('fulfilled:', val))
 .catch((err) => console.log('rejected', err));

// 等同於

p.then((val) => console.log('fulfilled:', val))
 .then(null, (err) => console.log("rejected:", err));

例子


getJSON('/post/1.json')
  .then(function(post) {
      return getJSON(post.commentURL);
      }) 
  .then(function(comments) {
      // some code
    })
  .catch(function(error) {
      // 處理前面三個Promise產生的錯誤
});


上面代碼中,一共有三個 Promise 對象:一個由getJSON產生,兩個由then產生。

它們之中任何一個拋出的錯誤,都會被最後一個catch捕獲。

promise.prototype.finally()

promise.prototype.finally()方法用於指定不管promise對象最後的狀態如何,都會執行的操作

  • finally() 方法的回調函數,不接受任何參數。
    ( 這就意味着,無法知道前面pormise實例對象最後的狀態是fulfilled還是rejected,也就是說,finally()函數中的操作與狀態無關,不依賴promise對象執行的結果 )
  • finally總是會返回之前的值

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});



上面代碼中,不管promise最後的狀態,在執行完then或catch指定的回調函數以後,

都會執行finally方法指定的回調函數。

Promise.all() -------- 注意,不在原型對象上

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

  • promise.all() 方法的參數可以不是數組,但是必須具有 iterator 接口,且返回的每個成員都是promise實例 ( 具有iterator接口,就是可遍歷的數據結構,可以被 for...of 遍歷 )
  • 注意:如果作爲參數的promise實例 ( 即Promise.all()實例子是rejected狀態 )自己定義了catch()方法,就不會觸發Promise.all()實例的 catch() 方法

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


上面代碼中,promise.all()方法,接受一個數組作爲參數,p1, p2, p3都是promise實例

promise.all() 方法的參數可以不是數組,但是必須具有 iterator 接口


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

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

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

例子

情況1:

// a,b,c都是promise實例對象
// 當a,b,c都是fulfilled狀態時, p 纔是fulfilled狀態,纔會觸發then的resolved狀態的回調函數
// p 的回調函數的參數,是a,b,c都變爲resolved狀態時的返回值組成的數組

componentDidMount() {
        let a = new Promise((resolve, reject) => {
            return resolve(1)
        })
        let b = new Promise((resolve,reject) => {
            return resolve(2)
        })
        let c = new Promise((resolve,reject) => {
            return resolve(3)
        })

        const p = Promise.all([a,b,c])     // a,b,c都是promise實例對象

        p.then(res => console.log(res))  // 輸出 [1, 2, 3]
}
情況2:

// a,b,c都是promise實例對象
// 當a,b,c中有一個是rejected狀態時,p的狀態就是rejected狀態,
// p 的回調函數的參數,是最先被rejected的Promse實例的返回值

componentDidMount() {
        let a = new Promise((resolve, reject) => {
            // return resolve(1)
            return reject(new Error('錯誤來自promise----a'))
        })
        let b = new Promise((resolve,reject) => {
            // return resolve(2)
            return reject(new Error('錯誤來自promise----b'))
        })
        let c = new Promise((resolve,reject) => {
            return resolve(3)
        })

        const p = Promise.all([a,b,c])  // p是rejected時,p的回調函數的參數是最先rejected的實例返回值

        p.then(res => console.log(res))
         .catch(err => console.log(err))  // 輸出    Error: 錯誤來自promise----a
}

特殊情況


const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('報錯了');
})
.then(result => result)
.catch(e => e);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 報錯了]



上面代碼中,p1會resolved,p2首先會rejected,

但是p2有自己的catch方法,該方法返回的是一個新的 Promise 實例,p2指向的實際上是這個實例。

該實例執行完catch方法後,也會變成resolved,導致Promise.all()方法參數裏面的兩個實例都會resolved,

因此會調用then方法指定的回調函數,而不會調用catch方法指定的回調函數。

Promise.race() --------- 對比Promise.all()

Promise.race()方法的作用同樣是將多個promise對象實例包裝成新的promise實例

  • race是賽跑,率先的意思
  • 規則: 如果p1, p2, p3 中實例對象的狀態有一個率先改變, p的狀態就會跟這改變 ( 無論是變爲fulfilled狀態,還是變爲 rejected狀態 ),p的回調函數的參數,是最先改變的那個promise實例的返回值

const p = Promise.race([       // Promise.race() 只要有一個參數狀態改變,p的狀態就是跟着改變
  fetch('/resource-that-may-take-a-while'),    // fetch返回的是promise對象
  new Promise(function (resolve, reject) {     // 5s後變爲rejected狀態
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);

p
.then(console.log)
.catch(console.error);



解析:
fetch在5s鍾內請求成功,p變成fulfilled狀態,觸發p的then()方法

fetch在5s中內請求失敗,p變爲rejected狀態,觸發p的catch()方法

Promise.resolve()

promise.resolve()可以將對象轉換爲promise對象

  • promise.resolve('foo') 等價於 new Promise(resolve => resolve('foo'))

Promise.resolve('foo')
// 等價於
new Promise(resolve => resolve('foo'))

Promise.resolve()方法的 參數

總結:
Promise.resolve()的參數無論是什麼類型 ( 或者不帶參數,或者參數本來就是一個promise對象,或者參數是一個thenable對象,或者參數是原始值,或者參數是普通對象 )本質上Promise.resolve()都會把參數轉化爲Promise對象,只是狀態分情況而已,比如:如果參數是thenable對象,會立即執行thenable對象的then方法,狀態當然後then方法中的函數決定,從而決定Promise.resolve()返回的promise對象的狀態
Promise.resolve()方法的參數分爲四種情況

  • 參數是一個promise實例對象
    如果Promise.resolve()方法的參數是一個( 實例對象 ),那麼Promise.resolve()方法將原封不動的( 返回這個實例對象 )

componentDidMount() {
        const foo = new Promise( (resolve, reject) => {
            return resolve('foo是一個promise實例對象')
        })
        const p = Promise.resolve(foo)     // Promise.resolve()的參數是一個promise實例
        console.log( p );    

    // 輸出: Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "foo是一個promise實例對象"}

}

  • 參數是一個thenable 對象
    如果Promise.resolve()方法的參數是 ( thenable對象 ),那麼Promise.resolve()方法會將這個對象轉化爲promise對象,然後立刻執行 thenable對象的 then 方法
什麼是 thenable 對象 ?

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

// 如果Promise.resolve()的參數是 thenable對象, Promise.resolve()方法會把thenable對象轉化爲promise對象
// 然後立刻執行thenable對象的then方法


componentDidMount() {
        const thenable = {    // thenable對象,裏面有then方法
            then: (resolve,reject) => resolve('這是thenable對象')
        }
        Promise.resolve(thenable)   // 參數是thenable對象,立刻執行thenable對象的then方法
            .then(res => console.log(res))  // thenable對象的的狀態是fulfilled,輸出其返回值
}

// 輸出:這是thenable對象
  • 參數不是thenable對象,或者根本不是一個對象
    如果參數是一個原始類型的值( 數字,字符串,布爾值 ),或者是一個不具有then方法的對象,則Promise.resolve()方法返回一個新的promise對象,狀態是fulfilled

 componentDidMount() {
    const str = 'abc'    
    const foo = Promise.resolve(str)
    // 參數是原始類型的值,Promise.resolve()方法會返回一個promise對象,狀態是resolved
            
    foo.then(res => console.log(res)) // 所以該回調會執行
}
  • 不帶參數
    當Promise.resolve()方法不帶參數是,直接返回一個promise對象,狀態是resolved

Promise.reject()方法

Promise.reject()方法返回一個promise實例對象,狀態是rejected

  • 和Promise.resolve()類似,只是Promise.rejected()方法的狀態一定是rejected

const p = Promise.reject('出錯了');
p.then(null, function (s) {
  console.log(s)
});
// 出錯了


// 等同於


const p = new Promise((resolve, reject) => reject('出錯了'))
p.then(null, function (s) {
  console.log(s)
});
// 出錯了

promise對象的應用

Promise.try()


const f = () => console.log('now');   // 函數f是一個同步事件
Promise.resolve().then(f);   // 通過Promise.resolve()返回一個promise對象,狀態是resolved,f變成異步
console.log('next'); 

// next       所以會先輸出next,再輸出now
// now

如何讓同步函數同步執行,異步函數異步執行,並且讓他們具有統一的api呢?

  • (1) 使用async函數

await 操作符用於等待一個 Promise 對象, 它只能在異步函數 async function 內部使用.


componentDidMount() {
        const funSync1 = () => console.log('我是同步函數1111111')
        const funSync2 = () => console.log('我是同步函數2222222')

        const funAsync = async () => {     // async關鍵字,定義的函數是異步函數,返回promise對象
            await funSync1()
        }
        funAsync().then(funSync2())
        console.log('bbbb')
}

// 先把兩個同步函數變成了異步,在異步函數中,先執行funSync1,後執行funSync2
// 使用async關鍵字後,會把同步包裝成的異步函數,按同步方式執行
// 所以最後得到的輸出順序是:


// 我是同步函數1111111
// 我是同步函數2222222
// bbbb
  • (2) 使用 new Promise()


const f = () => console.log('now');
(
  () => new Promise(
    resolve => resolve(f())
  )
)();
console.log('next');

// now
// next

  • (3) Promise.try() 提案


const f = () => console.log('now');
Promise.try(f);
console.log('next');

// now
// next


作者:省局到
鏈接:https://www.jianshu.com/p/4a937870511d
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章