Promise

一、promise概念和解决的问题

Promise,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大。

Promise就是一个对象容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从它可以获取异步操作的消息。

解决问题:回调地狱

回调函数地狱(callback hell):多个异步操作形成了强耦合,只要有一个操作需要修改,它的上层回调函数和下层回调函数,可能都要跟着修改。

Promise允许将回调函数的嵌套,改成链式调用

二、promise解决异步操作的优点

  • 链式操作减低了编码难度
  • Promise对象提供统一的接口,使得控制异步操作更加容易。
  • 代码可读性明显增强(Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。)

三、promise的缺点

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
  • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

注:如果某些事件不断地反复发生,使用 Stream 模式比部署Promise更好

四、有几种状态,分别是什么?

  • 正在进行 pending
  • 已成功 fulfilled
  • 已失败 rejected

五、Promise对象的特点

  1. 对象的状态不受外界影响。
  • Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
  1. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
  • Promise对象的状态改变,只有两种可能:从pending变为fulfilled从pending变为rejected。只要这两种情况发生,就会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。

六、promise的方法

1、 实例方法

(1)Promise.prototype.then()

then是实例状态发生改变时的回调函数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数

then方法返回的是一个新的Promise实例(不是原来那个Promise实例),也就是promise能链式书写的原因

getJSON("/posts.json").then(function(json) {

  return json.post;

}).then(function(post) {

  // ...

});

后一个回调函数,就会等待前一个回调函数返回的Promise对象的状态发生变化,才会被调用。

getJSON("/post/1.json").then(function(post) {
  return getJSON(post.commentURL);
}).then(function (comments) {
  console.log("resolved: ", comments);
}, function (err){
  console.log("rejected: ", err);
});

第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用第一个回调函数,如果状态变为rejected,就调用第二个回调函数

(2)Promise.prototype.catch()

catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数

getJSON('/posts.json').then(function(posts) {

  // ...

}).catch(function(error) {

  // 处理 getJSON 和 前一个回调函数运行时发生的错误

  console.log('发生错误!', error);

});

promise对象状态:

  • resolved–>then
  • rejected–>catch

then运行中发生错误–>catch

Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止

getJSON('/post/1.json').then(function(post) {

  return getJSON(post.commentURL);

}).then(function(comments) {

  // some code

}).catch(function(error) {

  // 处理前面三个Promise产生的错误

});

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

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)

一般来说,使用catch方法代替then()第二个参数
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

someAsyncThing()函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined,但是不会退出进程、终止脚本执行,2 秒之后还是会输出123。Promise 内部的错误不会影响到 Promise 外部的代码.

(3)Promise.prototype.finally()

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

promise

.then(result => {···})

.catch(error => {···})

.finally(() => {···});

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

server.listen(port)
  .then(function () {
    // ...
  })
  .finally(server.stop);

finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果

finally本质上是then方法的特例

promise
.finally(() => {
  // 语句
});

// 等同于
promise
.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);

finally方法总是会返回原来的值

2、构造函数方法

  1. all()
  2. race()
  3. allSettled()
  4. resolve()
  5. reject()
  6. try()
(1) all()

Promise.all()方法用于将多个 Promise实例,包装成一个新的 Promise实例

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

接受一个数组(迭代对象)作为参数,数组成员都应为Promise实例
实例p的状态由p1、p2、p3决定,分为两种:

  • 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数
  • 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数
    注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发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))

.catch(e => console.log(e));

// ["hello", Error: 报错了]

如果p2没有自己的catch方法,就会调用Promise.all()的catch方法

(2) race()

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例

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

只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变
率先改变的 Promise 实例的返回值则传递给p的回调函数

下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为reject,否则变为resolve。

const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);

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

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例
只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,返回的 Promise 对象才会发生状态变更。

const promises = [
  fetch('/api-1'),
  fetch('/api-2'),
  fetch('/api-3'),
];
await Promise.allSettled(promises);
removeLoadingIndicator();

只有等到这三个请求都结束了(不管请求成功还是失败),removeLoadingIndicator()才会执行。

该方法返回的新的 Promise 实例,一旦发生状态变更,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,它的回调函数会接收到一个数组作为参数,该数组的每个成员对应前面数组的每个 Promise 对象。

(4) any()

只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;
如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

Promise.any()抛出的错误,不是一个一般的 Error 错误对象,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被rejected的操作所抛出的错误。

// new AggregateError() extends Array

const err = new AggregateError();
err.push(new Error("first error"));
err.push(new Error("second error"));
// ...
throw err;

例子

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]
});
(5) resolve()

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

参数可以分成四种情况,分别如下:

  1. 参数是一个 Promise 实例
    promise.resolve将不做任何修改、原封不动地返回这个实例
  2. 参数是一个thenable对象(指的是具有then方法的对象)
    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
});
  1. 参数不是具有then()方法的对象,或根本就不是对象
    Promise.resolve()会返回一个新的 Promise 对象,状态为resolved
const p = Promise.resolve('Hello');

p.then(function (s) {
  console.log(s)
});
// Hello
  1. 没有参数时,直接返回一个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’)则是立即执行,因此最先输出。

(6) reject()

reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

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

const p = new Promise((resolve, reject) => reject('出错了'))

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

Promise.reject()方法的参数,会原封不动地变成后续方法的参数

Promise.reject('出错了')
.catch(e => {
  console.log(e === '出错了')
})
// true
(7) try()

让同步函数同步执行,异步函数异步执行的两种方法

1、用async函数

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

(async () => f())();
.then(...)
.catch(...)

console.log('next');
// now
// next

async () => f()会吃掉f()抛出的错误。所以,如果想捕获错误,要使用promise.catch方法。

2、new Promise()

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

使用立即执行的匿名函数,执行new Promise()

3、Promise.try()

const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
Promise.try(() => database.users.get({id: userId}))
  .then(...)
  .catch(...)

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

七、使用场景

1、加载图片

将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化

const preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    const image = new Image();
    image.onload  = resolve;
    image.onerror = reject;
    image.src = path;
  });
};

2、Generator 函数与 Promise 的结合

使用 Generator 函数管理流程,遇到异步操作的时候,通常返回一个Promise对象。

function getFoo () {
  return new Promise(function (resolve, reject){
    resolve('foo');
  });
}

const g = function* () {
  try {
    const foo = yield getFoo();
    console.log(foo);
  } catch (e) {
    console.log(e);
  }
};

function run (generator) {
  const it = generator();

  function go(result) {
    if (result.done) return result.value;

    return result.value.then(function (value) {
      return go(it.next(value));
    }, function (error) {
      return go(it.throw(error));
    });
  }

  go(it.next());
}

run(g);

3、all()实际应用

通过all()实现多个请求合并在一起,汇总所有请求结果,只需设置一个loading即可

function initLoad(){
    // loading.show() //加载loading
Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{
        console.log(res)
        loading.hide() //关闭loading
    }).catch(err=>{
        console.log(err)
        loading.hide()//关闭loading
    })
}
//数据初始化    
initLoad()

4、race() 实际应用

通过race可以设置图片请求超时

//请求某个图片资源

function requestImg(){

    var p = new Promise(function(resolve, reject){

        var img = new Image();

        img.onload = function(){

           resolve(img);

        }

        img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg1";

    });

    return p;

}


//延时函数,用于给请求计时

function timeout(){

    var p = new Promise(function(resolve, reject){

        setTimeout(function(){

            reject('图片请求超时');

        }, 5000);

    });

    return p;

}
Promise

.race([requestImg(), timeout()])

.then(function(results){

    console.log(results);

})

.catch(function(reason){

    console.log(reason);

});

附上原文链接:阮一峰ES6——promise

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值