你的浏览器不支持canvas

深入浅出:promise的各种用法

https://mp.weixin.qq.com/s?__biz=MzAwNTAzMjcxNg==&mid=2651425195&idx=1&sn=eed6bea35323c75f0c43ae61818c0a55&chksm=80dff7c8b7a87edeb834cc4aabf0eec40c7566b45abd5c58b56625dc0efd77d15c9e64534140&mpshare=1&scene=1&srcid=02260hYIB6d5lSLVwyvPIUWX#rd 

Promise是一个构造函数,自己身上有all、reject、resolve这几个眼熟的方法,原型上有then、catch等同样很眼熟的方法。 

var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('执行完成');
resolve('随便什么数据');
}, 2000);
});

二、使用promise的好处是:
1.代码结构更加扁平且更可读,清晰明了。

2.能解决回调地狱问题。

3.可将数据请求和业务逻辑分离开来。

4.便于管理维护。

5.能更好的捕获错误。

1.优点和缺点
可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
 
Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 

2.Promise规范如下:

一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)
一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换
promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致
then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个promise传入,也接受一个“类then”的对象或方法,即thenable对象。

3.promise的特性:

(1.)立即执行性 

var p=new Promise(function(resolve,reject)(){
console.log("create new promise");
resolve("success");
});
 
console.log("after new  promise");
 
p.then(function(value){
console.log(value);
});
//create new promise
//after new  promise
//success
 

(2.)状态不可逆,链式调用

var p = new Promise(function(resolve, reject){
  resolve(1);
});
p.then(function(value){               //第一个then
  console.log(value);
  return value*2;
}).then(function(value){              //第二个then
  console.log(value);
}).then(function(value){              //第三个then
  console.log(value);
  return Promise.resolve('resolve'); 
}).then(function(value){              //第四个then
  console.log(value);
  return Promise.reject('reject');
}).then(function(value){              //第五个then
  console.log('resolve: '+ value);
}, function(err){
  console.log('reject: ' + err);
})
//1
//2
//undefined
//resolve
//reject: reject

 

 (3.)回调异步性

var p = new Promise(function(resolve, reject){
  resolve("success");
});
 
p.then(function(value){
  console.log(value);
});
 
console.log("first");
//"first"
//"success"
 

4.用法

function getURL(URL) {

 

      return new Promise(function (resolve, reject) {
          var req = new XMLHttpRequest();
          req.open('GET', URL, true);
          req.onload = function () {
              if (req.status === 200) {
                  resolve(req.responseText);
              } else {
                  reject(new Error(req.statusText));
              }
          };
          req.onerror = function () {
              reject(new Error(req.statusText));
          };
          req.send();
      });
}
// 运行示例
var URL = "http://httpbin.org/get";
getURL(URL).then(function onFulfilled(value){
  console.log(value);
}).catch(function onRejected(error){
  console.error(error);
});
 

 原文参考:https://blog.csdn.net/qq_29849641/article/details/54970328

 三、promise的回调地狱

 

一般我们要在一个函数执行完之后执行另一个函数我们称之为callback‘回调’,简单的写一下:
setTimeout(function () {
left(function () {
setTimeout(function () {
left(function () {
setTimeout(function () {
left();
}, 2000);
});
}, 2000);
});
}, 2000);
  以上代码就是传说中的回调地狱,如果有多层业务逻辑嵌套的话,不仅会使代码阅读困难,而且后面维护起来也是难点。
之后在ES6,Promise就应运而生。

四、promise的than用法

var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
   resolve(value)是在Promise在已经异步完成成功(Resolved)之后执行的
    reject(value)是在Promise在异步失败之后(Rejected)执行。
    当然,也可以用then来指定:then(resolve, reject)
    或者:then(resolve),catch (reject)
promise.then(function(value) {
// success
}, function(error) {
// failure
});
//等价于:
promise.then(function(){
//success
}).catch(function(){
//failure
})
总结:

 

1.可以采用连续的then链式操作来写回调(这是因为返回值一直是新的Promise实例)。
2.以上例子可以看出来只要在第一个promise回调中添加resolve,之后的连续then就会默认执行。
 
可以在then中return出数据,并且这个数据会以参数的形式传入下一个then。
var p = new Promise(function (resolve, reject) {
var a = 1
resolve(a);

}).then(function (data) {
console.log(data)
return ++data;
}).then(function (data) {
console.log(data)
})

五、reject的用法

reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调。看下面的代码。

function getNumber() {
var p = new Promise(function (resolve, reject) {
//做一些异步操作
setTimeout(function () {
var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
if (num <= 5) {
resolve(num);
}
else {
reject('数字太大了');
}
}, 2000);
});
return p;
}

getNumber()
.then(
function (data) {
console.log('resolved');
console.log(data);
},
function (reason, data) {
console.log('rejected');
console.log(reason);
}
);
getNumber函数用来异步获取一个数字,2秒后执行完成,如果数字小于等于5,我们认为是“成功”了,调用resolve修改Promise的状态。否则我们认为是“失败”了,调用reject并传递一个参数,作为失败的原因。

运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到下面两种结果:

六、catch的用法(Promise.prototype.catch())

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
我们知道Promise对象除了then方法,还有一个catch方法,它是做什么用的呢?其实它和then的第二个参数一样,用来指定reject的回调,用法是这样:
getNumber()
.then(function (data) {
console.log('resolved');
console.log(data);
})
.catch(function (reason) {
console.log('rejected');
console.log(reason);
});

效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。请看下面的代码:
getNumber()
.then(function (data) {
console.log('resolved');
console.log(data);
console.log(somedata); //此处的somedata未定义
})
.catch(function (reason) {
console.log('rejected');
console.log(reason);
});

  在resolve的回调中,我们console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到这样的结果: 

  也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。

七、all的用法

  Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。我们仍旧使用上面定义好的runAsync1、runAsync2、runAsync3这三个函数,看下面的例子:

Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function (results) {
console.log(results);
});
用Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。那么,三个异步操作返回的数据哪里去了呢?都在then里面呢,all会把所有异步操作的结果放进一个数组中传给then,就是上面的results。所以上面代码的输出结果就是:

  有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,是不是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。
八、race的用法
  all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。race的用法与all一样,我们把上面runAsync1的延时改为1秒来看一下:

Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function (results) {
console.log(results);
});

你猜对了吗?不完全,是吧。在then里面的回调开始执行时,runAsync2()和runAsync3()并没有停止,仍旧再执行。于是再过1秒后,输出了他们结束的标志。

这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:

//请求某个图片资源
function requestImg() {
var p = new Promise(function (resolve, reject) {
var img = new Image();
img.onload = function () {
resolve(img);
}
img.src = 'xxxxxx';
});
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);
});

requestImg函数会异步请求一张图片,我把地址写为"xxxxxx",所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。运行结果如下:
九、Promise.prototype.finally()
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的
promise
.then(result => { ··· })
.catch(error => { ··· })
.finally(() => { ··· });
上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
下面是一个例子,服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。

 pasting

server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
finally本质上是then方法的特例。
promise
.finally(() => {
// 语句
});

// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
上面代码中,如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。
它的实现也很简单。
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
上面代码中,不管前面的 Promise 是fulfilled还是rejected,都会执行回调函数callback。
从上面的实现还可以看到,finally方法总是会返回原来的值。
// resolve 的值是 undefined
Promise.resolve(2).then(() => { }, () => { })

// resolve 的值是 2
Promise.resolve(2).finally(() => { })

// reject 的值是 undefined
Promise.reject(3).then(() => { }, () => { })

// reject 的值是 3
Promise.reject(3).finally(() => { })
十、Promise.try()
实际开发中,经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法。
Promise.resolve().then(f)
上面的写法有一个缺点,就是如果f是同步函数,那么它会在本轮事件循环的末尾执行。
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
上面代码中,函数f是同步的,但是用 Promise 包装了以后,就变成异步执行了。
那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?回答是可以的,并且还有两种写法。第一种写法是用async函数来写。
const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
上面代码中,第二行是一个立即执行的匿名函数,会立即执行里面的async函数,因此如果f是同步的,就会得到同步的结果;如果f是异步的,就可以用then指定下一步,就像下面的写法。
(async () => f())()
.then(...)

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

 pasting

(async () => f())()
.then(...)
.catch(...)
第二种写法是使用new Promise()。
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)();
console.log('next');
// now
// next
上面代码也是使用立即执行的匿名函数,执行new Promise()。这种情况下,同步函数也是同步执行的。
鉴于这是一个很常见的需求,所以现在有一个提案,提供Promise.try方法替代上面的写法。
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
事实上,Promise.try存在已久,Promise 库Bluebird、Q和when,早就提供了这个方法。
由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。这样有许多好处,其中一点就是可以更好地管理异常。
function getUsername(userId) {
return database.users.get({id: userId})
.then(function(user) {
return user.name;
});
}

上面代码中,database.users.get()返回一个 Promise 对象,如果抛出异步错误,可以用catch方法捕获,就像下面这样写。
database.users.get({ id: userId })
.then(...)
.catch(...)
但是database.users.get()可能还会抛出同步错误(比如数据库连接错误,具体要看实现方法),这时你就不得不用try...catch去捕获。

 pasting

try {
database.users.get({ id: userId })
.then(...)
.catch(...)
} catch (e) {
// ...
}

 

上面这样的写法就很笨拙了,这时就可以统一用promise.catch()捕获所有同步和异步的错误。
Promise.try(database.users.get({ id: userId }))
.then(...)
.catch(...)
事实上,Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。

原网站:https://www.cnblogs.com/whybxy/p/7645578.html

https://www.cnblogs.com/sunshq/p/7890504.html

推荐阮一峰promise:http://es6.ruanyifeng.com/#docs/promise" target="_blank" >

posted @ 2018-08-31 18:02  云上丶无忧  阅读(2555)  评论(0编辑  收藏  举报

来场流星雨,吧