1. 程式人生 > 實用技巧 >promise和await async進階

promise和await async進階

寫部落格,既能梳理了專題知識,又加深記憶和理解,好了,不廢話,正文馬上開始。

1. promise作用

作用:promise解決回撥地獄的問題

2.promise基本用法

一定要記得 new Promise(executor) 的 executor 是馬上執行的;

promise、then、finally都是微任務;

let myPromise = new promise(function(resolved,rejected){})

Promise結合setTimeout

微任務包括:MutationObserver、Promise.then()或catch()、Promise為基礎開發的其它技術,比如fetch API、V8的垃圾回收過程、Node獨有的process.nextTick。

巨集任務包括:script 、setTimeout、setInterval 、setImmediate 、I/O 、UI rendering。

event loop它的執行順序:

  1. 一開始整個指令碼作為一個巨集任務執行
  2. 執行過程中同步程式碼直接執行,巨集任務進入巨集任務佇列,微任務進入微任務佇列
  3. 當前巨集任務執行完出隊,檢查微任務列表,有則依次執行,直到全部執行完
  4. 執行瀏覽器UI執行緒的渲染工作
  5. 檢查是否有Web Worker任務,有則執行
  6. 執行完本輪的巨集任務,回到2,依此迴圈,直到巨集任務和微任務佇列都為空

按照event loop規則就能很快輸出結果

Promise中的then、catch、finally

  1. Promise的狀態一經改變就不能再改變;
  2. .then和.catch都會返回一個新的Promise;
  3. catch不管被連線到哪裡,都能捕獲上層未捕捉過的錯誤;
  4. 在Promise中,返回任意一個非 promise 的值都會被包裹成; promise物件,例如return 2會被包裝為return Promise.resolve(2);
async function async1 () {
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')

'srcipt start'
'srcipt end'
'async1 end'

複製程式碼
  1. Promise 的 .then 或者.catch可以被呼叫多次,但如果Promise內部的狀態一經改變,並且有了一個值,那麼後續每次呼叫.then或者.catch的時候都會直接拿到該值;
  2. .then 或者 .catch 中 return 一個 error; 物件並不會丟擲錯誤,所以不會被後續的 .catch 捕獲;
  3. .then 或 .catch 返回的值不能是 promise 本身,否則會造成死迴圈;
  4. .then 或者 .catch 的引數期望是函式,傳入非函式則會發生值透傳;
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)
  //1  因為發生了透傳
複製程式碼
  1. .then方法是能接收兩個引數的,第一個是處理成功的函式,第二個是處理失敗的函式,再某些時候你可以認為catch是.then第二個引數的簡便寫法;
  2. .finally方法也是返回一個Promise,他在Promise結束的時候,無論結果為resolved還是rejected,都會執行裡面的回撥函式。

就像是這裡的finally()會等promise1().then()執行完才會將finally()加入微任務佇列

其實你只要記住它三個很重要的知識點就可以了:

  • .finally()方法不管Promise物件最後的狀態如何都會執行
  • .finally()方法的回撥函式不接受任何的引數,也就是說你在.finally()函式中是沒法知道Promise最終的狀態是resolved還是rejected的
  • 它最終返回的預設會是一個上一次的Promise物件值,不過如果丟擲的是一個異常則返回異常的Promise物件。
const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve('resolve3');
    console.log('timer1')
  }, 0)
  resolve('resovle1');
  resolve('resolve2');
}).then(res => {
  console.log(res)
  setTimeout(() => {
    console.log(p1)
  }, 1000)
}).finally(res => {
  console.log('finally', res)
})

'resolve1'
'finally' undefined
'timer1'
Promise{<resolved>: undefined}

分析:
- Promise的狀態一旦改變就無法改變
- finally不管Promise的狀態是resolved還是rejected都會執行,且它的回撥函式是接收不到Promise的結果的,所以finally()中的res是一個迷惑項(類似3.10)。
- 最後一個定時器打印出的p1其實是.finally的返回值,我們知道.finally的返回值如果在沒有丟擲錯誤的情況下預設會是上一個Promise的返回值, 
- 而這道題中.finally上一個Promise是.then(),但是這個.then()並沒有返回值,所以p1打印出來的Promise的值會是undefined,如果你在定時器的下面加上一個return 1,返回是1。


複製程式碼

Promise中的all、race

  • Promise.all()的作用是接收一組非同步任務,然後並行執行非同步任務,並且在所有非同步操作執行完後才執行回撥。
  • .race()的作用也是接收一組非同步任務,然後並行執行非同步任務,只保留取第一個執行完成的非同步操作的結果,其他的方法仍在執行,不過執行結果會被拋棄。
  • Promise.all().then()結果中陣列的順序和Promise.all()接收到的陣列順序一致。
  • all和race傳入的陣列中如果有會丟擲異常的非同步任務,那麼只有最先丟擲的錯誤會被捕獲,並且是被then的第二個引數或者後面的catch捕獲;但並不會影響陣列中其它的非同步任務的執行。

3. await async

正常情況下,async中的await命令是一個Promise物件,返回該物件的結果。

但如果不是Promise物件的話,就會直接返回對應的值,相當於Promise.resolve()

async function fn () {
  // return await 123
  // 等同於
  return 123
}
fn().then(res => console.log(res)) //123
複製程式碼
async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')

在async1中await後面的Promise是沒有返回值的,也就是它的狀態始終是pending狀態,
因此相當於一直在await,await,await卻始終沒有響應...,
所以在await之後的內容是不會執行的,也包括async1後面的 .then。

'script start'
'async1 start'
'promise1'
'script end'
複製程式碼

注意

async function async1 () {
  await new Promise(resolve => {
    console.log('promise1')
    resolve('promise resolve')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => {
  console.log(res)
})
這道過有一點需要注意的,在async1中的newPromise它的resovle的值和async1().then()裡的值是沒有關係的,小夥伴可能看到resovle('promise resolve')就會誤以為是async1().then()中的返回值。

'script start'
'promise1'
'async1 success'
'async1 end'  //此處是返回值,不是resovle('promise resolve')

複製程式碼

await 的異常處理

如果在async函式中丟擲了錯誤,則終止錯誤結果,不會繼續向下執行。

以下例子使用reject和Error,都中斷執行

async function async1 () {
  await async2();
  console.log('async1');
  return 'async1 success'
}
async function async2 () {
  return new Promise((resolve, reject) => {
    console.log('async2')
    reject('error')
  })
}
async1().then(res => console.log(res))

'async2'
Uncaught (in promise) error
複製程式碼

改為throw new Error

async function async1 () {
  console.log('async1');
  throw new Error('error!!!')
  return 'async1 success'
}
async1().then(res => console.log(res))
複製程式碼

如果想要使得錯誤的地方不影響async函式後續的執行的話:

  • 可以使用try catch
  • 可以直接在Promise.reject後面跟著一個catch()方法:
async function async1 () {
  // try {
  //   await Promise.reject('error!!!')
  // } catch(e) {
  //   console.log(e)
  // }   //此方法也行
  await Promise.reject('error!!!')
    .catch(e => console.log(e))
  console.log('async1');
  return Promise.resolve('async1 success')
}
async1().then(res => console.log(res))
console.log('script start')

'script start'
'error!!!'
'async1'
'async1 success'
此處注意catch執行在console.log('async1')之前,不像then的執行順序
複製程式碼

await用法的缺點和優點

優點:

  • 同步程式碼思想
  • 寫法上簡單

缺點:

  • 有些執行沒必要等待

4. Promise和await async

  • Promise結合await async

記住緊跟著await後面的語句相當於放在了new Promise中,下一行及之後的語句相當於放在於Promise.then中

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
async1();
console.log('start')

複製程式碼
  • Promise和await async的區別

Promise不會阻塞後面同步程式碼的執行,await會阻塞

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  setTimeout(() => {
    console.log('timer')
  }, 0)
  console.log("async2");
}
async1();
console.log("start")

執行結果
'async1 start'
'async2'
'start'
'async1 end'
'timer'
定時器始終還是最後執行的,它被放到下一條巨集任務的延遲佇列中。
複製程式碼

5. promise手寫程式碼

  1. v1.0 初始版本myPromise
function myPromise(contructor){
    let self = this
    self.status = "pending";
    self.value = undefinded;
    self.reason = undefinded;
    
    function resolved(value){
        if(self.status === 'pending'){
            self.status = 'resolved';
            self.value = value;
        }
    }
    function rejected(){
        if(self.status === 'pending'){
            self.status = 'rejected';
            self.reason = reason;
        }
    }
    contructor(resolved,rejected)
}
myPromise.prototype.then = function(onFullFilled,onRejected){
    let self = this;
    switch(this.status){
        case 'resolved':
            onFullFilled(self.value)
        case 'rejected':
            onRejected(self.value)
    }
}
複製程式碼
  1. v2.0 基於觀察者模式實現

用2個數組onFullfilledArray和onRejectedArray來儲存非同步的方法。在狀態發生改變時,一次遍歷執行陣列中的方法。

function myPromise(contructor){
    let self = this
    self.status = "pending";
    self.value = undefinded;
    self.reason = undefinded;
    self.onFullfilledArray = [];
    self.onRejectedArray = [];
    
    function resolved(value){
        if(self.status === 'pending'){
            self.status = 'resolved';
            self.onFullfilledArray.forEach(function(f){
                f(self.value)
                //如果狀態從pending變為resolved,那麼就遍歷執行裡面的非同步方法
            })
            
        }
    }
    function rejected(){
        if(self.status === 'pending'){
            self.status = 'rejected';
            self.onRejectedArray.forEach(function(f){
                f(self.reason)
                //如果狀態從pending變為rejected,那麼就遍歷執行裡面的非同步方法
            })
        }
    }
    contructor(resolved,rejected)
}
myPromise.prototype.then = function(onFullFilled,onRejected){
    let self = this;
    switch(this.status){
        case 'pending':
            self.onFullfilledArray.push(function(){
                onFullFilled(self.value)
            }
            self.onRejectedArray.push(function(){
                onFullFilled(self.reason)
            }
        case 'resolved':
            onFullFilled(self.value)
            break;
        case 'rejected':
            onRejected(self.reason)
            break;
        default;
    }
}
複製程式碼

通過兩個陣列,在狀態發生改變之後再開始執行,這樣可以處理非同步resolve無法呼叫的問題。這個版本的myPromise就能處理所有的非同步,那麼這樣做就完整了嗎?

  1. v3.0 then方法實現鏈式呼叫
myPromise.prototype.then = function(onFullFilled,onRejected){
    let self = this;
    let newPromise;
    switch(this.status){
        case 'pending':
            <!--then返回promise-->
            return newPromise = new myPromise(function(resolved,rejected){
                self.onFullfilledArray.push(function(){
                    try{
                       let temple=onFullfilled(self.value); //得到resolved的值
                       resolve(temple)
                    }catch(e){
                       reject(e) //error catch
                    }
                });
                self.onFullfilledArray.push(function(){
                    try{
                       let temple=onRejected(self.reason); //得到rejected的值
                       reject(temple)
                    }catch(e){
                       reject(e) //error catch
                    }
                });
            })
        case 'resolved':
            return newPromise = new myPromise(function(resolve,reject){
                try{
                  let temple=onFullfilled(self.value);
                  //將上次一then裡面的方法傳遞進下一個Promise的狀態
                  resolve(temple);
                }catch(e){
                  reject(e);//error catch
                }
            })
            break;
        case 'rejected':
            return newPromise = new myPromise(function(resolve,reject){
            try{
               let temple=onRejected(self.reason);
               //將then裡面的方法傳遞到下一個Promise的狀態裡
               resolve(temple);   
            }catch(e){
               reject(e);
            }
        })
            break;
        default;
    }
}

複製程式碼

這樣通過then方法返回一個promise就可以實現鏈式的呼叫:

p.then(function(x){
    console.log(x)
})
.then(function(){
    console.log("鏈式呼叫1")
})
.then(function(){
    console.log("鏈式呼叫2")
})
//輸出
1
鏈式呼叫1
鏈式呼叫2
複製程式碼

這樣我們雖然實現了then函式的鏈式呼叫,但是還有一個問題,就是在Promise/A+規範中then函式裡面的onFullfilled方法和onRejected方法的返回值可以是物件,函式,甚至是另一個promise。 疑問:以上onFullfilled方法和onRejected方法有返回值嗎?

4.v4.0 then函式中的onFullfilled和onRejected方法的返回值問題

function resolvePromise(promise,x,resolve,reject){
  if(promise===x){
     throw new TypeError("type error")
  }
  let isUsed;
  if(x!==null&&(typeof x==="object"||typeof x==="function")){
      try{
        let then=x.then;
        if(typeof then==="function"){
           //是一個promise的情況
           then.call(x,function(y){
              if(isUsed)return;
              isUsed=true;
              resolvePromise(promise,y,resolve,reject);
           },function(e){
              if(isUsed)return;
              isUsed=true;
              reject(e);
           })
        }else{
           //僅僅是一個函式或者是物件
           resolve(x)
        }
      }catch(e){
         if(isUsed)return;
         isUsed=true;
         reject(e);
      }
  }else{
    //返回的基本型別,直接resolve
    resolve(x)
  }
}
複製程式碼

改變了resolvePromise函式之後,我們在then方法裡面的呼叫也變成了resolvePromise而不是promise。

myPromise.prototype.then=function(onFullfilled,onRejected){
    let self=this;
    let promise2;
    switch(self.status){
      case "pending":
        promise2=new myPromise(function(resolve,reject){
             self.onFullfilledArray.push(function(){
                setTimeout(function(){
                  try{
                       let temple=onFullfilled(self.value);
                       resolvePromise(temple)
                    }catch(e){
                       reject(e) //error catch
                    }
                })
             });
             self.onRejectedArray.push(function(){
                setTimeout(function(){
                   try{
                       let temple=onRejected(self.reason);
                       resolvePromise(temple)
                     }catch(e){
                       reject(e)// error catch
                   }
                })
             });
        })
      case "resolved":
        promise2=new myPromise(function(resolve,reject){
           setTimeout(function(){
               try{
                  let temple=onFullfilled(self.value);
                  //將上次一then裡面的方法傳遞進下一個Promise狀態
                  resolvePromise(temple);
                }catch(e){
                  reject(e);//error catch
               }
           })
        })
        break;
      case "rejected":
        promise2=new myPromise(function(resolve,reject){
           setTimeout(function(){
             try{
               let temple=onRejected(self.reason);
               //將then裡面的方法傳遞到下一個Promise的狀態裡
               resolvePromise(temple);   
             }catch(e){
               reject(e);
             }
           })
        })
        break;
      default:       
   }
   return promise2;
}
複製程式碼

6. 如何讓非同步順序執行

var arr = [1, 2, 3, 4]
var promises = []

arr.map(async (value) => {
  promises.push(new Promise((res) => {
    setTimeout(() => {
      console.log(value)
      res()
    }, 1000)
  }))
})

var promise = Promise.resolve()

for (var i = 0; i < promises.length; i += 1) {
  const task = promises[i];
  promise.then(() => {
      return task
    })
}
複製程式碼
arr.reduce((p, x) => p.then(() => new Promise(r => setTimeout(() => r(console.log(x)), 1000))), Promise.resolve())
複製程式碼
const arr = [1, 2, 3]
arr.reduce((p, x) => {
  return p.then(() => {
    return new Promise(r => {
      setTimeout(() => r(console.log(x)), 1000)
    })
  })
}, Promise.resolve())
複製程式碼

注意掌握reduce的高階用法,此處我還得多寫幾個例子加深理解

7. 場景題目

使用Promise實現每隔1秒輸出1,2,3

做此題先寫迴圈方法程式碼,不能滿足要求

function print(){
    for (let i=1;i<4;i++){
        console.log('first:',i)
        new Promise(()=>{
            console.log('second:',i)
            setTimeout(function(){
                console.log(i++)
            },1000)
        })
    }
}
print() 

async function print(){
  let a = [1,2,3]
  for (let i of a){
    console.log('first',i)
    await new Promise((resolved)=>{
      resolved(i)
    }).then(()=>{
    console.log('second:',i)
      setTimeout(function(){
          console.log(i)
      },1000)})
}

}
print()

//都是先輸出
> "first" 1
> "second:" 1
> "first" 2
> "second:" 2
> "first" 3
> "second:" 3
再1s後同時並行輸出
> 1
> 2
> 3
原因:在一個迴圈作用域下,執行非同步

new Promise是微任務,setTimeout是巨集任務
主程式完成之後,執行微任務,巨集任務壓入執行佇列中
複製程式碼
async function print(){
  let a = [1,2,3]
  for (let i of a){
    console.log('first',i)
    await new Promise((resolved)=>{
      resolved(i)
    }).then(()=>{
    console.log('second:',i)
     // setTimeout(function(){
          console.log(i)
     // },1000)
    })
    }
}


> "first" 1
> "second:" 1
> 1
> "first" 2
> "second:" 2
> 2
> "first" 3
> "second:" 3
> 3
複製程式碼
function print(i){
  return new Promise((resolved)=>{
      setTimeout(function(){
          console.log(i)
            resolved(i)
      },2000)
      
    })
    


}
print(1).then(()=>{
    print(2)
}).then(()=>{
    print(3)
})

2s後先出現1,再出現2,3,因為setTimeout最後面執行
> 1
> 2
> 3
複製程式碼
function print(i){
  return new Promise((resolved)=>{
      setTimeout(function(){
          console.log(i)
            resolved(i)
      },2000)
      
    })
    
}
async function a(i){
  await print(i)
}
a(1)
a(2)
a(3)

2s後同時出現
> 1
> 2
> 3
複製程式碼
var arr = [1, 2, 3, 4]
var promises = []

arr.map(async (value) => {
  promises.push(new Promise((res) => {
    setTimeout(() => {
      console.log(value)
      res()
    }, 2000)
  }))
})

var promise = Promise.resolve()


for (var i = 0; i < promises.length; i += 1) {
  const task = promises[i]
  promise
    .then(() => {
      return task
    })
}

2s後同時出現
> 1
> 2
> 3
> 4
複製程式碼

把let改成var

function print(){
    for (var i=1;i<4;i++){
        console.log('first:',i)
        new Promise(()=>{
            console.log('second:',i)
            setTimeout(function(){
                console.log(i++)
            },1000)
        })
    }
}
print() 
//先輸出
> "first" 1
> "second:" 1
> "first" 2
> "second:" 2
> "first" 3
> "second:" 3
再1s後同時並行輸出
> 4
> 5
> 6

因為先執行了for迴圈,再執行new Promise

當非同步事件發生時,會建立事件並放入執行佇列中,等待當前程式碼執行完成之後再執行這些程式碼。
複製程式碼

如何讓非同步操作順序執行 最終程式碼:

const arr = [1, 2, 3]
arr.reduce((p, x) => {
  return p.then(() => {
    return new Promise(r => {
      setTimeout(() => r(console.log(x)), 1000)
    })
  })
}, Promise.resolve())

部分變成非箭頭函式
const arr = [1, 2, 3]
arr.reduce((p, x) => {
  return p.then(() => {
    return new Promise(function(resolved){
        setTimeout(function(){
          resolved(console.log(x))
          //console.log(x)  如果沒有resolved()包含console.log(x),則僅僅1s後輸出1
        }, 1000)
    })
  })
}, Promise.resolve())

要知道resolved是個函式

每隔1s後輸出
> 1
> 2
> 3
複製程式碼

紅燈3秒亮一次,黃燈2秒亮一次,綠燈1秒亮一次,使用Promise實現紅綠燈交替重複亮?

function red() {
    console.log('red');
}
function green() {
    console.log('green');
}
function yellow() {
    console.log('yellow');
}


new Promise((resolve)=>{
    setTimeout(()=>{
        red()
        resolve()
    },3000)
}).then((resolve) => {
    setTimeout(()=>{
        green()
        //resolve()  
    },2000)
  }).then((resolve) => {
    setTimeout(()=>{
        yellow()
     // resolve()
    },1000)
  })
  
> "red"
> "yellow"
> "green"


報錯:VM5613:22 Uncaught TypeError: resolve is not a function
要知道then後面沒有resolved函式

複製程式碼

下面換return方式試:

function print(timer,cb){
    return new Promise(function(resolve){
        setTimeout(() => {
          cb()
          resolve()
        }, timer)
    })
} 
Promise.resolve().then(() => {
    return print(3000, red)  
}).then(() => {
    return print(2000, green)
  }).then(() => {
    return print(1000, yellow)
  })
 
 3s後輸出"red",2s後輸出"green",1s後輸出"yellow"
> "red"
> "green"
> "yellow"

我自己的解釋:Promise.resolve方法允許呼叫時不帶引數,直接返回一個resolved狀態的 Promise 物件,所以回撥函式會立即執行;then()函式裡不返回值或者返回的不是promise,那麼 then 返回的 Promise 將會成為接受狀態(resolve)

`都是在then之後返回print,也就是new Promise`

new Promise(()=>{
    return print(3000, red)
}).then(() => {
    return print(2000, green)
 }).then(() => {
    return print(1000, yellow)
})



3s後輸出
> "red"

new Promise((resolve)=>{
    //return new Promise(function(resolve){
        setTimeout(() => {
          red()
          resolve()
        }, 3000)
    //})
}).then(() => {
    return new Promise(function(resolve){
        setTimeout(() => {
          green()
          resolve()
        }, 2000)
    })
 }).then(() => {
    return new Promise(function(resolve){
        setTimeout(() => {
          yellow()
          resolve()
        }, 2000)
    })
})
註釋then之前的return,為了獲取resolve狀態,這個很能對比Promise.resolve()

> 3s後輸出"red",2s後輸出"green",1s後輸出"yellow"
複製程式碼

這個想象讓我問自己為什麼第一行必須寫Promise.resolve()

來了解下new Promise()和Promise.resolve()的區別

Promise.resolve()

1)如果引數是一個Promise例項,Promise.resolve將不做任何修改、原封不動地返回這個例項


let v = new Promise(resolve => {
    console.log("begin");
    //resolve("then");
});
Promise.resolve(v)

> "begin"

let v = new Promise(resolve => {
    console.log("begin");
    resolve("then");
});
Promise.resolve(v).then((res)=>console.log(res))
    
> "begin"
> "then"
複製程式碼

2)如果引數是一個包含then的物件

Promise.resolve方法會將這個物件轉為 Promise 物件例項

let thenable = {
    then: function(resolve, reject) {
        resolve(42);
    }
}
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});
複製程式碼

3)引數不是具有then方法的物件,或根本就不是物件,也不是Promise例項

Promise.resolve方法返回一個新的 Promise 物件,狀態為resolved

const p = Promise.resolve('HHY');

console.log(p) //Promise{<resolved>: "HHY"}
 
p.then(function (s){
  console.log(s)
});

> HHY

複製程式碼

4)不帶任何引數

直接返回一個resolved狀態的 Promise 物件


console.log(Promise.resolve()) //Promise{<resolved>: undefined}

setTimeout(function () {
  console.log('three');
}, 0);
 
Promise.resolve().then(function () {
  console.log('two');
});
 
console.log('one');
 
> "one"
> "two"
> "three"

複製程式碼

.then()函式裡不返回值或者返回的不是promise,那麼 then 返回的 Promise 將會成為接受狀態(resolve)

Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3)
);
console.log(1); 

> 1
> 2
> 3
複製程式碼

先輸出1的原因:

Promise.resolve(),是在本輪“事件迴圈”(event loop)的結束時執行,不是馬上執行,也不是在下一輪“事件迴圈”的開始時執行.

原因:傳遞到 then() 中的函式被置入了一個微任務佇列,而不是立即執行,這意味著它是在 JS 事件佇列的所有執行時結束了,事件佇列(我的理解是同步程式碼的事件佇列)被清空之後,才開始執行.

new Promise()

let v = new Promise(resolve => {
  console.log("begin");
  resolve("then");
});

複製程式碼

在promise裡面resolve一個狀態為fulfilled的promise

resolve()本質作用:

resolve()是用來表示promise的狀態為fullfilled,相當於只是定義了一個有狀態的Promise,但是並沒有呼叫它; promise呼叫then的前提是promise的狀態為fullfilled; 只有promise呼叫then的時候,then裡面的函式才會被推入微任務中;

區別

  • new promise是返回一個promise物件
  • Promise.resolve() 返回一個promise物件,狀態為resolved
  • Promise.resolve方法允許呼叫時不帶引數,直接返回一個resolved狀態的 Promise 物件,所以回撥函式會立即執行;then()函式裡不返回值或者返回的不是promise,那麼 then 返回的 Promise 將會成為接受狀態(resolve)

因此new promise的操作就跟你 new 一個普通函式沒區別,所以這一句其實是巨集任務,但後面的then是微任務

resolved後的promise物件會在這該級別事件佇列結束之後才開始執行,及執行與該輪微任務佇列中,始於下一級別巨集任務之前

Promise.resolve()可以返回例項物件。

Promise.resolve(v)不等於new Promise(r => r(v))

當v是一個Promise例項的時候就會出現一些不同的地方

    // v是一個例項化的promise,且狀態為fulfilled
    let v = new Promise(resolve => {
      console.log("begin");
      resolve("then");
    });
 
 
    模式一 new Promise裡的resolve()
    
    結果: begin->1->2->3->then->4 可以發現then推遲了兩個時序
    推遲原因:瀏覽器會建立一個 PromiseResolveThenableJob 去處理這個 Promise 例項,這是一個微任務。
    等到下次迴圈到來這個微任務會執行,也就是PromiseResolveThenableJob 執行中的時候,因為這個Promise 例項是fulfilled狀態,所以又會註冊一個它的.then()回撥
    又等一次迴圈到這個Promise 例項它的.then()回撥執行後,
    才會註冊下面的這個.then(),於是就被推遲了兩個時序
    new Promise(resolve => {
      console.log(v) //Promise{<resolved>: "then"}
      console.log(resolve(v)) //undefined
      resolve(v);
    }).then((v)=>{
        console.log(v) //獲取then值得地方
    });  //begin->1->2->3->then->4 
    
    我的理解:resolve中包含例項物件,先輸出begin,再執行下面輸出1,2,3(1理解,為什麼會執行了2和3,再then呢?),再來執行獲取then值得地方
 
    模式二 Promise.resolve(v)直接建立
    
    結果:begin->1->then->2->3->4 可以發現then的執行時間正常了,第一個執行的微任務就是下面這個.then
    原因:Promise.resolve()API如果引數是promise會直接返回這個promise例項,不會做任何處理

     Promise.resolve(v).then((v)=>{
        console.log(v)
    }); //begin->1->then->2->3->4
 
    new Promise(resolve => {
      console.log(1);
      resolve();
    })
      .then(() => {
        console.log(2);
      })
      .then(() => {
        console.log(3);
      })
      .then(() => {
        console.log(4);
      });

> "begin"
> 1
> "then"
> 2
> 3
> 4


> "begin"
> 1
> 2
> 3
> "then"
> 4

複製程式碼

實現mergePromise函式

把傳進去的陣列按順序先後執行,並且把返回的資料先後放到陣列data中。

const time = (timer) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, timer)
  })
}
const ajax1 = () => time(2000).then(() => {
  console.log(1);
  return 1
})
const ajax2 = () => time(1000).then(() => {
  console.log(2);
  return 2
})
const ajax3 = () => time(1000).then(() => {
  console.log(3);
  return 3
})

function mergePromise (ajaxArray) {
  // 存放每個ajax的結果
  const data = [];
  let promise = Promise.resolve();
  ajaxArray.forEach(ajax => {
    // 第一次的then為了用來呼叫ajax
    // 第二次的then是為了獲取ajax的結果
    promise = promise.then(ajax).then(res => {
      data.push(res);
      return data; // 把每次的結果返回
    })
  })
  // 最後得到的promise它的值就是data
  return promise;

}

mergePromise([ajax1, ajax2, ajax3]).then(data => {
  console.log("done");
  console.log(data); // data 為 [1, 2, 3]
});


複製程式碼

根據promiseA+實現一個自己的promise

封裝一個非同步載入圖片的方法

,只需要在圖片的onload函式中,使用resolve返回一下就可以了。

function loadImg(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = function() {
      console.log("一張圖片載入完成");
      resolve(img);
    };
    img.onerror = function() {
        reject(new Error('Could not load image at' + url));
    };
    img.src = url;
  });


複製程式碼

限制非同步操作的併發個數並儘可能快的完成全部

8. 參考連結

github.com/fortheallli…

juejin.im/post/684490…

9. 後語

花了一週時間整理,一些標記是自己不熟悉的地方,另外一些昇華題需要繼續研究,有時間繼續補充和更正,請大家閱讀,有收穫請給個