2

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函数,用来将操作失败后报出的错误传递出去

clipboard.png

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)
  })

输出为:

clipboard.png

提示错误未捕获,所以需要在then()方法中添加第二个回到函数来处理出错信息

  fn1().then(fn2).then((data) => {
    console.log(data)
  }, (err) => {
    console.log(err)
  })

输出为:

clipboard.png

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在开发中常见的用法,参考了以下几篇文章,特此感谢作者


喔喔牛在路上
55 声望3 粉丝