promise用法解析

喔喔牛在路上發表於2018-02-01

1.概述

javascript是單執行緒語言(單執行緒/多執行緒、阻塞/非阻塞、同步、非同步)參考此文章,所有的任務都是按順序執行的,但是對於很耗時的操作採用非同步的方式(前一個任務結束的時候,不是執行下一個任務,而是執行回撥函式,後一個任務則是不等前一個任務結束就執行)參考此文章,js中非同步程式設計的四種方法:回撥函式、事件監聽、釋出/訂閱、Promise物件,這裡討論promise的用法,promise是es6增加的內容,使得在非同步程式設計中不用過多的巢狀回撥函式,讓非同步操作變得更加簡單

2.Promise介紹

2.1 Promise建構函式

Promise建構函式接收一個函式作為引數,此函式又有兩個引數分別為resolve和reject,這兩個引數也是函式

  const promise = new Promise(function(resolve, reject) {
  // 非同步操作的程式碼
  if(success) {
    return resolve(data); // data為非同步操作成功返回的資料
  } else {
    return reject(error); //data為失敗時返回的資料
  }
})

複製程式碼

2.2 resolve和reject

promise物件類似於一個容器(也可以說是一個狀態機),裡面包含著非同步操作,非同步操作會有兩種結果:成功或失敗。當非同步操作成功就會將pending(執行中狀態)轉為fulfilled(成功狀態)同時觸發resolve函式,用來將操作成功後得到的結果傳遞出去;當非同步操作失敗就會將pending(執行中狀態)轉為reject(拒絕狀態)同時觸發reject函式,用來將操作失敗後報出的錯誤傳遞出去

promise用法解析

const promise = new Promise(function(resolve, reject) {
  // 非同步操作的程式碼
  if(success) {
    return resolve(data); // data為非同步操作成功返回的資料
  } else {
    return reject(error); //data為失敗時返回的資料
  }
})
複製程式碼

我們來看下面程式碼:

  function fn1() {
    return new Promise(function(resolve, reject){
      setTimeout(() => {
        console.log('fn1')
      },1000)
    })
  }
  function fn2() {
    return new Promise(function(resolve, reject){
      setTimeout(() => {
        console.log('fn2')
      },2000)
    })
  }
fn1().then(fn2)
複製程式碼

輸出為:
fn1
fn2函式不執行,這個時候我們需要呼叫resolve函式以便執行then()方法中的回撥函式fn2

function fn1() {
    return new Promise(function(resolve, reject){
      setTimeout(() => {
        console.log('fn1')
        resolve('fn1')
      },1000)
    })
  }
複製程式碼

輸出為:
fn1
fn2
如果我們在fn1中呼叫reject函式時會出現什麼情況呢?

function fn1() {
    return new Promise(function(resolve, reject){
      setTimeout(() => {
        console.log('fn1')
        // resolve('fn1')
        reject('錯誤'+'fn1')
      },1000)
    })
  }
  fn1().then(fn2).then((data) => {
    console.log(data)
  })
複製程式碼

輸出為:

promise用法解析

提示錯誤未捕獲,所以需要在then()方法中新增第二個回到函式來處理出錯資訊

  fn1().then(fn2).then((data) => {
    console.log(data)
  }, (err) => {
    console.log(err)
  })
複製程式碼

輸出為:

promise用法解析

2.3 then()方法

當promise例項生成以後,後面跟then方法,其的第一個回撥函式來處理resolve函式傳遞的資料,第二個回撥函式來處理reject函式傳遞的資料,以上的流程用程式碼展示如下

promise
  .then(function(data){
    //拿到返回的資料之後做一些處理
    console.log(data)
  }, function(error) {
    //返回失敗時做一些處理
    console.log(error)
  })
複製程式碼

2.3.1 then()的返回值

then()方式是Promise例項的方法,此then方法定義在原型物件(Promise.prototype)上,then()方法的返回值是一個新的Promise例項(不是原來那個Promise,原來那個Promise已經承諾過)所以我們可以採用鏈式的寫法

var p1 = new Promise( (resolve, reject) => {
    setTimeout(() => resolve('p1'), 10);
});

p1.then( ret => {
    console.log(ret);
    return 'then1';
}).then( ret => {
    console.log(ret);
    return 'then2';
}).then( ret => {
    console.log(ret);
});
複製程式碼

執行順序:
p1>then1>then2
從第二個then()方法開始,它們的resolve中的引數就是前一個then()中的resolve的return語句的返回值 採用鏈式的then,可以指定一組按照次序呼叫的回撥函式。這時,前一個回撥函式,有可能返回的還是一個Promise物件(即有非同步操作),這時後一個回撥函式,就會等待該Promise物件的狀態發生變化,才會被呼叫

getJSON("/post/1.json").then(function(post) {
  return getJSON(post.commentURL);}).then(function funcA(comments) {
  console.log("resolved: ", comments);}, function funcB(err){
  console.log("rejected: ", err);}) 
複製程式碼

上面程式碼中,第一個then方法指定的回撥函式,返回的是另一個Promise物件。這時,第二個then方法指定的回撥函式,就會等待這個新的Promise物件狀態發生變化。如果變為resolved,就呼叫funcA,如果狀態變為rejected,就呼叫funcB。 如果採用箭頭函式,上面的程式碼可以寫得更簡潔

getJSON("/post/1.json").then(
  post => getJSON(post.commentURL)).then(
  comments => console.log("resolved: ", comments),
  err => console.log("rejected: ", err));
複製程式碼

promise鏈式寫法如下:

  function fn1() {
    return new Promise(function(resolve, reject){
      setTimeout(() => {
        console.log('fn1')
        resolve('fn1')
      },1000)
    })
  }
  function fn2() {
    return new Promise(function(resolve, reject){
      setTimeout(() => {
        console.log('fn2')
        resolve('fn2')
      },2000)
    })
  }
  function fn3() {
    return new Promise(function(resolve, reject){
      setTimeout(() => {
        console.log('fn3')
        resolve('fn3')
      },3000)
    })
  }
  function fn4() {
    return new Promise(function(resolve, reject){
      setTimeout(() => {
        console.log('fn4')
        resolve('fn4')
      },4000)
    })
  }
  fn1().then(fn2).then(fn3).then((data) => {
    console.log(data)
  }, (err) => {
    console.log(err)
  })
複製程式碼

依次輸出為:
fn1>fn2>fn3
同時我們還可以更改執行的先後順序

  function fn1() {
    return new Promise(function(resolve, reject){
      setTimeout(() => {
        console.log('fn1')
        reject(false)
      },1000)
    })
  }
  function fn2() {
    return new Promise(function(resolve, reject){
      setTimeout(() => {
        console.log('fn2')
        resolve('fn2')
      },2000)
    })
  }
  function fn3() {
    return new Promise(function(resolve, reject){
      setTimeout(() => {
        console.log('fn3')
        resolve('fn3')
      },3000)
    })
  }
  function fn4() {
    return new Promise(function(resolve, reject){
      setTimeout(() => {
        console.log('fn4')
        resolve('fn4')
      },4000)
    })
  }
  fn1().then(fn2).then(fn3).then((data) => {
    console.log(data)
  }, (err) => {
    if(err == false){
      fn3().then(fn4)
    }
  })
複製程式碼

輸出為:
fn1>fn3>fn4

2.4 catch()方法

我們在開發時傾向於用catch()方法來處理異常,而不是在then()方法裡寫入第二個回撥函式,這種寫法類似於.then(null, rejection)

promise
  .then(function(data){
    //拿到返回的資料之後做一些處理
    console.log(data)
  })
  .catch(function(error) {
    //返回失敗時做一些處理
    console.log(error)
  }

複製程式碼

為什麼要採用這麼catch()方法呢?我們來看一個例子:

const promise = new Promise((resolve,reject) => {
  console.log(1)
  resolve('成功')
})
promise
  .then((data) => {
    console.log(data)
    console.log(a)
  }, (err) => {
    console.log(err)
  })

複製程式碼

輸出為:
1
成功
但是沒有捕捉到回撥函式裡a未定義 這個時候我們來該寫以上程式碼
1
成功
ReferenceError: a is not defined
Promise物件的Error物件具有冒泡性質,會一直向後傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch語句捕獲

var p = new Promise( (resolve, reject) => {
    setTimeout(() => resolve('p1'), 10);
});

p.then( ret => {
    console.log(ret);
    throw new Error('then1');
    return 'then1';
}).then( ret => {
    console.log(ret);
    throw new Error('then2');
    return 'then2';
}).catch( err => {
    // 可以捕抓到前面的出現的錯誤。
    console.log(err.toString());
});

複製程式碼

輸出為:
p1
Error: then1

2.4.1 catch()返回值

既然catch()是.then(null, rejection)的別名,那麼catch()就會返回一個Promise物件,因此在後面還可以接著呼叫then方法

var p = new Promise((resolve, reject) => {
    resolve(x + 2);
});
p.then( () => {
    console.log('nothing');
}).catch( err => {
    console.log(err.toString());
    return 'catch';
}).then( ret => {
    console.log(ret);
});

複製程式碼

輸出為:
ReferenceError: x is not defined
catch
當出錯時,catch會先處理之前的錯誤,然後通過return語句,將值繼續傳遞給後一個then方法中,如果沒有報錯,則跳過catch,示例如下:

var p = new Promise((resolve, reject) => {
    resolve('p');
});
p.then( ret => {
    console.log(ret);
    return 'then1';
}).catch( err => {
    console.log(err.toString());
    return 'catch';
}).then( ret => {
    console.log(ret);
});

複製程式碼

3. promise用法解析

3.1 用Promise實現ajax操作

const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject){
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出錯了', error);
});

複製程式碼

3.2 promise和ajax方法結合

var http = {
    get: function(url) {
        var promise = new Promise(function(resolve, reject) {
            $.ajax({
                url: url,
                method: 'get',
                success: function(data) {
                    resolve(data);
                },
                error: function(xhr, statusText) {
                    reject(statusText);
                }
            });
        });
        return promise;
    }
};
http.get('data.php').then(function(data) {
    document.write(data);
}, function(err) {
    document.write(err);
});

複製程式碼

3.3 用promise實現非同步載入圖片的例子

function loadImageAsync(url) {
  return new Promise(function(resolve, reject) {
    const image = new Image();

    image.onload = function() {
      resolve(image);
    };

    image.onerror = function() {
      reject(new Error('Could not load image at ' + url));
    };

    image.src = url;
  });
}
var loadImage1 = loadImageAsync(url);
loadImage1.then(function success() {
    console.log("success");
}, function fail() {
    console.log("fail");
});

複製程式碼

3.4 promise和axios方法結合

function fetch(url, params) {
  return new Promise((resolve, reject) => {
    axios.post(url, params)
      .then(response => {
        resolve(response.data);
      }, error => {
        reject(error);
      })
      .catch(error => {
        reject(error)
      })
  })
}

function lineStatus(params) {
  return fetch('/ProductionLine/GetStatus', params)
}
function statisticsData(params) {
  return fetch('/ProductionLine/GetWeightStatistics', params)
}

      lineStatus(this.lineId)
        .then((result) => {
          this.deviceStatus.run = result.TotleMoveMotor
          this.deviceStatus.stop = result.TotleStopMotor
          this.deviceStatus.lost = result.TotleLoseMotor
          this.deviceStatus.alarm = result.TotleAlarmMotor
          this.ProductionStatus = result.ProductionStatus
          console.log(result)
        })
        .catch((error) => {
          console.log('瓦特了...(;′⌒`)')
        })

      statisticsData(this.lineId)
        .then((result) => {
          this.outputData.totalOutput = result.MainConveyorModel.OutPut
          this.outputData.instantWeight = result.MainConveyorModel.InstantWeight
          this.outputData.runningTime = result.MainConveyorModel.RunningTime
          this.outputData.motorLoad = result.MainConveyorModel.MotorLoad
          // console.log(result)
        })
        .catch((error) => {
          console.log('瓦特了...(;′⌒`)')
        })

複製程式碼

以上是Promise在開發中常見的用法,參考了以下幾篇文章,特此感謝作者

相關文章