ES6(10)Promise(异步编程的一种解决方案)

特点

1、对象的状态不受外界影响。

有三种状态:pending(进行中)fulfilled(已成功)rejected(已失败)

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。

状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected

Promise缺点

1、无法取消Promise,一旦新建它就会立即执行,无法中途取消

2、如果不设置回调函数,Promise内部抛出的错误,不会反应到外部

3、当处于pending状态时,无法得知目前进展到哪一个阶段

只要fulfilledrejected这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)

如果改变已经发生了,再对Promise对象添加回调函数,也会立即得到这个结果

事件的特点是:如果你错过了它,再去监听,是得不到结果

Promise事件(Event)完全不同

基本用法

Promise 是一个构造函数

1、const promise = new Promise(function(resolve, reject) {});

2、new Promise 返回一个 promise对象, 接收一个excutor函数作为参数

3、excutor有两个函数类型形参resolve(成功) reject(失败),第二个函数是可选的,不一定要提供

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

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
复制代码

4、Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数

var _promise = new Promise(function(resolve, reject){
    setTimeout(function(){
        var rand = Math.random();
        if(rand<0.5){
            resolve("resolve" + rand);
        }else{
            reject("reject" + rand);
        }
    },1000);

});
_promise.then(result => { // onFulfilled
    console.log(result); // 'fulfilled'
}).catch(reason => { // onRejected 不会被调用
    console.log(reason)
})
复制代码

5、Promise 新建后就会立即执行

Promise 新建后立即执行,所以首先输出的是“Promise”

then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

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

console.log('Hi!');

// Promise
// Hi!
// resolved
复制代码

异步加载图片

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;
  });
}
复制代码

用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);
});
复制代码

resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例

一个异步操作的结果是返回另一个异步操作

const p1 = new Promise(function (resolve, reject) {
    console.log(3)
    setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
    console.log(1)
    setTimeout(() => resolve(p1), 1000)
})

p2.then(result => console.log(result,2))
  .catch(error => console.log(error,4))
//3   //promise 创建就执行
//1   //promise 创建就执行
// Error: fail   4    //不执行2执行4 是因为  p2的状态(执行哪个回调)由p1决定

复制代码

p2 resolve 一个p1,p2的状态由p1的状态决定

上面代码p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。

p2 resolve 一个p1,导致p2自己的状态无效了,由p1的状态决定p2的状态,后面的then语句都变成针对后者(p1

p1是一个 Promise,3 秒之后变为rejectedp2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的(resolve)是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。

调用resolve或reject并不会终结 Promise 的参数函数的执行

一般来说,调用resolvereject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

//虽然能执行但是 没有任何作用
new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1

//正常推荐写法
new Promise((resolve, reject) => {
  return resolve(1);
  // 后面的语句不会执行
  console.log(2);
})
复制代码

promise的三种状态

  • pending (进行中)
  • fulfilled(已成功)
  • rejected(已失败)

1.promise 对象初始化状态为 pending

2.当调用resolve(成功),会由pending => fulfilled

3.当调用reject(失败),会由pending => rejected

pending => fulfilled/rejected, 一旦修改就不能再变

promise对象方法

Promise.prototype.then(resolved,rejected)方法

Promise 实例添加状态改变时的回调函数(resolve(成功)/reject(失败)的回调函数)

第一个参数是resolved状态的回调函数

第二个参数(可选)是rejected状态的回调函数

返回值:then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)

// onFulfilled 是用来接收promise成功的值
// onRejected 是用来接收promise失败的原因
promise.then(onFulfilled, onRejected);
复制代码

resolve(成功) onFulfilled会被调用

const promise = new Promise((resolve, reject) => {
   resolve('fulfilled'); // 状态由 pending => fulfilled
});
promise.then(result => { // onFulfilled
    console.log(result); // 'fulfilled' 
}, reason => { // onRejected 不会被调用

})
复制代码

reject(失败) onRejected会被调用

const promise = new Promise((resolve, reject) => {
   reject('rejected'); // 状态由 pending => rejected
});
promise.then(result => { // onFulfilled 不会被调用

}, reason => { // onRejected 
    console.log(reason); // 'rejected'
})
复制代码

Promise.prototype.catch() ,用于指定发生错误时的回调函数

在链式写法中可以捕获前面then中发送的异常

catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法

如果catch中没有错误,就是resolvePromise对象

promise.catch(onRejected)相当于promise.then(null, onRrejected)promise.then(undefined, rejection)的别名

catch能捕获的错误:

– 1、异步操作reject抛出错误
– 2、then方法指定的回调函数,运行中抛出错误(代码语法错误)

注意: then函数的第二个参数onRejected 不能捕获当前onFulfilled中的异常(代码语法错误)

一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),建议使用catch方法。

Promise 对象后面一定要跟catch方法,这样可以处理 Promise 内部发生的错误。

promise.then(onFulfilled, onRrejected); 

//可以写为
promise.then(function(posts) {
  // ...
}).catch(function(error) {
  // 处理 getJSON 和 前一个回调函数运行时发生的错误
  console.log('发生错误!', error);
});
复制代码

Promise 对象的错误具有“冒泡”性质(总是会被下一个catch语句捕获)

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

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 内部的错误不会影响到 Promise 外部的代码

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  console.log('everything is great');
});

setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
复制代码

Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined,但是不会退出进程、终止脚本执行,2 秒之后还是会输出123

Promise.catch后的链式调用

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing()
.catch(function(error) {
  console.log('oh no', error);
})
.then(function() {
  console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on
复制代码
如果没有报错,则会跳过catch方法,直接执行后面的then方法
Promise.resolve()
.catch(function(error) {
  console.log('oh no', error);
})
.then(function() {
  console.log('carry on');
});
// carry on
复制代码
catch方法抛出一个错误会被下一个catch捕获 ,后面没有catch错误不会被捕获,也不会传递到外层
someAsyncThing().then(function() {
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
  // 下面一行会报错,因为y没有声明
  y + 2;
}).catch(function(error) {
  console.log('carry on', error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]
复制代码

Promise.prototype.finally(),用于指定不管Promise 对象最后状态如何,都会执行的操作

用于指定不管Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

不管promise最后的状态,在执行完thencatch指定的回调函数以后,都会执行finally方法指定的回调函数。

finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
复制代码

服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。

server.listen(port)
  .then(function () {
    // ...
  })
  .finally(server.stop);
复制代码

Promise 链式调用

promise.then方法每次调用 都返回一个新的promise实例 所以可以链式写法

function taskA() {
    console.log("Task A");
}
function taskB() {
    console.log("Task B");
}
function onRejected(error) {
    console.log("Catch Error: A or B", error);
}

var promise = Promise.resolve();
promise
    .then(taskA)
    .then(taskB)
    .catch(onRejected) // 捕获前面then方法中的异常 

复制代码

1、链式调用 依次传递结果(参数)

前一个回调函数完成以后,会将返回结果作为参数,传入下一个回调函数。

const p = new Promise(function (resolve, reject) {
    setTimeout(() => resolve('aa'), 1000)
})
p.then((e) => {
    console.log(e)  //aa
    return e;
}).then((e2) => {
    console.log(e)   //aa
    //如果前面的then没有 return e  ,e2就是undefined
})
复制代码

2、前一个回调函数,返回的还是一个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);
});
复制代码

3、使用箭头函数

getJSON("/post/1.json").then(
  post => getJSON(post.commentURL)
).then(
  comments => console.log("resolved: ", comments),
  err => console.log("rejected: ", err)
);
复制代码

Promise的静态方法

Promise.resolve 将现有对象转为 Promise 对象

返回一个fulfilled状态的promise对象

Promise.resolve('hello').then(function(value){
    console.log(value);   
});
//hello



Promise.resolve('hello');
// 相当于
new Promise(resolve => {
   resolve('hello');
});
复制代码

Promise.resolve方法的参数分成四种情况

(1)参数是一个 Promise 实例,那么Promise.resolve不做任何修改、原封不动地返回这个实例

如果参数是 Promise 实例,那么Promise.resolve不做任何修改、原封不动地返回这个实例。

(2)参数是一个thenable对象(具有then方法的对象),Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法

thenable对象指的是具有then方法的对象,比如下面这个对象。

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};
复制代码

Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。

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

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});
复制代码
(3)参数不是具有then方法的对象,或根本就不是对象,返回一个新的 Promise 对象,状态为resolved

如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved

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

p.then(function (s){
  console.log(s)       // Hello
}).catch(function (s){
  console.log(s)       //p状态为`resolved`  不执行
});

复制代码
(4)不带有任何参数,直接返回一个resolved状态的 Promise 对象

直接返回一个resolved状态的 Promise 对象。

const p = Promise.resolve();

p.then(function () {
  // ...
});
复制代码
立即resolve()Promise对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时
setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three
复制代码

上面代码中:

setTimeout(fn, 0)在下一轮“事件循环”开始时执行
Promise.resolve()在本轮“事件循环”结束时执行
console.log(‘one’)则是立即执行,因此最先输出。

Promise.reject 返回一个rejected状态的promise对象

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
  console.log(s)
});
// 出错了
复制代码

Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。

const thenable = {
  then(resolve, reject) {
    reject('出错了');
  }
};

Promise.reject(thenable)
.catch(e => {
  console.log(e === thenable)
})
// true
复制代码

Promise.all 接收一个promise对象数组为参数

用于将多个 Promise 实例,包装成一个新的 Promise 实例

Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例

如果不是,就会先调用讲到的Promise.resolve方法(返回一个fulfilled状态的promise对象),将参数转为 Promise 实例,再进一步处理。

Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

p的状态由p1、p2、p3决定,分成两种情况:

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数
(2)只要p1、p2、p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数
const p1 = new Promise((resolve, reject) => {
    resolve(1);
});

const p2 = new Promise((resolve, reject) => {
    resolve(2);
});

const p3 = new Promise((resolve, reject) => {
    reject(3);
});

Promise.all([p1, p2, p3]).then(function(data){ 
    console.log(data); // [1, 2, 3] 结果顺序和promise实例数组顺序是一致的
}).catch(function(err) {
    console.log(err);
});
复制代码

下面代码中,p1resolvedp2首先会rejected

但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。

该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会
resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。

如果p2没有自己的catch方法,就会调用Promise.all()catch方法,如上边代码所示

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))  // ["hello", Error: 报错了]
.catch(e => console.log(e));

复制代码

Promise.race 接收一个promise对象数组为参数,只要有一个调用完成 ,后面的调用不执行,直接调用Promise.race

function timerPromisefy(delay) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve(delay);
        }, delay);
    });
}
var startDate = Date.now();

Promise.race([
    timerPromisefy(10),
    timerPromisefy(20),
    timerPromisefy(30)
]).then(function (values) {
    console.log(values); // 10
});
复制代码

Promise.allSettled()接受一组 Promise 实例作为参数,等到所有这些参数实例都返回结果才会结束

方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由 ES2020 引入。

该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled不会变成rejected。状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()Promise 实例。

每个对象都有status属性,该属性的值只可能是字符串fulfilled或字符串rejected

fulfilled时,对象有value属性,

rejected时有reason属性,对应两种状态的返回值。

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(function (results) {
  console.log(results);
});
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]
复制代码

Promise.any():只要有一个fulfilled状态,包装实例就会变成fulfilled状态,全部rejected包装实例才会变成rejected状态,跟Promise.race()很像

该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态

Promise.any()Promise.race()方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected状态而结束。

const promises = [
  fetch('/endpoint-a').then(() => 'a'),
  fetch('/endpoint-b').then(() => 'b'),
  fetch('/endpoint-c').then(() => 'c'),
];
try {
  const first = await Promise.any(promises);
  console.log(first);
} catch (error) {
  console.log(error);
}
复制代码

上面代码中,Promise.any()方法的参数数组包含三个 Promise 操作。其中只要有一个变成fulfilledPromise.any()返回的 Promise 对象就变成fulfilled。如果所有三个操作都变成rejected,那么await命令就会抛出错误。

捕捉错误时,如果不用try...catch结构和 await 命令,可以像下面这样写:

Promise.any(promises).then(
  (first) => {
    // Any of the promises was fulfilled.
  },
  (error) => {
    // All of the promises were rejected.
  }
);
复制代码

例子

var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);

Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
  console.log(result); // 42
});

Promise.any([rejected, alsoRejected]).catch(function (results) {
  console.log(results); // [-1, Infinity]
});
复制代码

Promise.try(),不管f是否包含异步操作,都用then方法指定下一步流程

实际开发中,经常遇到一种情况:

不知道或者不想区分,函数f是同步函数还是异步操作,但是都想用 Promise 来处理它。

Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。

因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。

一般就会采用下面的写法。

Promise.resolve().then(f)
复制代码

上面的写法有一个缺点,就是如果f是同步函数,那么它会在本轮事件循环的末尾执行

const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
复制代码

让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API

1、async函数

const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
复制代码

上面代码中,第二行是一个立即执行的匿名函数,会立即执行里面的async函数,因此如果f是同步的,就会得到同步的结果;

如果f是异步的,就可以用then指定下一步,就像下面的写法。

(async () => f())()
.then(...)
.catch(...)   //async () => f()会吃掉f()抛出的错误,使用promise.catch捕获
复制代码

2、new Promise()

const f = () => console.log('now');
(
  () => new Promise(
    resolve => resolve(f())
  )
)();
console.log('next');
// now
// next
复制代码
使用Promise.try方法替代上面的自执行函数写法,Promise.try(f)
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
复制代码
如果想用then方法管理流程,最好都用Promise.try包装一下,可以更好地管理异常
Promise.try(() => database.users.get({id: userId}))
  .then(...)
  .catch(...)
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享