Promise in Javascript
promise you have a good time here !
The Promise object is used for deferred and asynchronous computations.
简单来说,promise 是一个用来处理延迟和异步的对象。
故事要从回调开始说起
callback(回调) 是 javascript 默认的用来处理异步请求的方法。
通常我们将一个函数 A 当做另一个函数 B 的参数传入,在未来满足某个条件的情况下,函数 B 会调用 A 。例如:
function loadImg(src,parent,callback){
var img=document.createElement('img');
img.src=src;
img.onload=callback; // 图片加载完成后调用回调函数
parent.appendChild(img);
}
回调的代码是可以正常工作的。那么它会有什么问题呢?
如果这里的callback也需要一个回调参数呢?然后这个回调参数又需要另一个回调?你知道这个问题是可以无限进行下去的吧。。。于是就有了这样的代码,传说中的 Pyramid of Doom ! 金字塔式的代码调用。
image.png
这里问题就很明显了
- 错误处理:每一个回调都要单独进行错误处理。
- 调试困难
那么如果我们的代码可以写成这样呢:
image.png
Promise
4个阶段:stages of promise
- Pending : waiting.一般初始化一个promise对象时的状态就是pending,这个时候还没有成功或者失败。
- Fulfilled (resolve) : It worked ! 相关的方法成功啦!
- **Rejected **: It didn't work ! 相关的方法失败啦!
- **Settled **: Someting happend ! 方法处理完了,它要么成功了,要么失败了。
包裹 :wrapping
**promise is a try-catch wrapper around code that will finished at an unpredictable time. **
异步代码的特点就是我们不知道它什么时候结束。promise 就是对这类代码的一层包装而已,nothing more. 这样包装的好处就是不用再像之前的回调地狱那样来写代码。
那么 promise 的代码怎么写呢:coding
promise是一个构造函数
image.png
- 任何传递给resolve或者reject的参数都会作为参数被传给下一个then(或者catch)中的处理方法。
- 如果没有传递任何参数,那么接收到的就是undefined.
- 如果resolve方法里的参数也是一个promise,那么这个promise会先被执行,然后它的resolve里的参数会被传给下一个then. 注意,resolve后会执行链上的下一个then, reject会执行链上的下一个catch.
小练习1:包装一个图片请求
function loadImg(src,parent,callback){
var img=document.createElement('img');
img.src=src;
img.onload=callback; // 图片加载完成后调用回调函数
parent.appendChild(img);
}
对这段代码用promise包装一下,如下:
new Promise(function(resolve,reject){
var img=document.createElement('img');
img.src='image.jpg';
img.onload=resolve,
img.onerror=reject;
document.body.appenndChild(img);
})
.then(finishLoading)
.catch(showAlternateImage);
一旦 resolve 或者 reject 被调用了,就说明这个 promise 已经被处理的(settled)了,然后方法链上的下一个then(或者catch)开始被调用。
小练习2:包装一个setTimeout
<script>
function wait(ms){
/*
要求:
1. 用 promise 来保证 setTimeout 方法,在 setTimeout的回调里调用resolve()
2.注意要让wait方法返回这个promise
*/
window.setTimeout(function(){},ms);
}
</script>
答案:
function wait(ms){
return new Promise(function(resolve){
setTimeout(function(){
resolve('hello world');
},ms);
});
}
wait(300).then(function(data){
console.log("this is "+data);
});
小练习3:包装一个xhr
function get(url){
var req=new XMLHttpRequest();
req.open('GET',url);
req.onload=function(){
if(req.status==200){
// 请求成功,可以把返回数据res.response传给resolve方法中
}else{
// 请求失败,可以把失败信息 req.statusText传给reject中
};
};
req.onerror=function(){
//请求失败,把"Network Error" 传给reject
};
req.send();
}
答案:
function get(url){
return new Promise(function(resolve,reject){
var req=new XMLHttpRequest();
req.open('GET',url);
req.onload=function(){
if(req.status==200){
resolve(req.response);
}else{
reject(req.statusText);
}
};
req.onerror=function(){
reject(Error("Network Error"));
};
req.send();
});
}
//调用
get("http://www.test.com/")
.then(function(res){
console.log(res);
})
.catch(function(res){
//handle the error
});
Fetch API
前面的 xhr对象 和 promise 的包装代码是不是很烦人?获取数据不应该这么麻烦。chromn里自带的 fetch API 可以让我们省去很多 xhr 对象的麻烦设置,而且,it is built on native promise !
Fetch API 用法:
fetch('https://www.test.com/',{
method:'get'
}).then(function(response){
}).catch(function(error){
});
记得上一章写的那个很长的 get(url) 吗?额。。。其实它只要这么写就可以了...
function get(url){
return fetch(url,{
method:'get'
}
}
thenable
then方法返回的是一个 promise 对象,我们可以在一个初始方法(一般返回promise对象)上调用后连接 then方法,也可以在 then 方法后面连接 then 方法,因为它自身返回的也是 promise对象。开发者一般用 thenable 这个词来描述 promise 对象和 .thens.
any method or object that returns .then is thenable.
anything thenable can become a part of chain of asynchronous work.
当创建一个异步调用链时,链上的每个方法接收到的,要么是前面的 promise 成功时传递过来的参数,或者是前一个 then 方法返回的值。这样你就可以在异步的方法之间传递参数。
be able to chain thenables 对于简化异步代码的顺序调用非常有用。
Error Handling Strategies 错误处理策略
image.png注意,这两种错误处理是一样的。.catch() 方法 其实是 .then ( undefined , rejectFunc ) 的缩写而已。例如,
get('example.json').then(resolveFunc,rejectFunc) ,如果get方法里有错误的话,rejectFunc就会被调用。
总的来说,一旦promise 是rejected的,javascript引擎就会跳转到调用链上的下一个rejectJFunc,无论这个方法是写在catch里还是then里。
但还是建议使用catch方法,因为可读性强一些,并且写起来方便一些。
注意,.catch 和第二个回调的rejectFunc在执行的顺序会有不同。如下所示:
image.png
上面的代码块,如果resolveFunc出错了,那么下面catch里的这个rejectFunc就会被执行。这两个方法是可以同时被执行的。但如果用下面代码行的写法,同一个 then 里的两个方法,只有一个会被执行,或者两个都不执行,如果这里resolveFunc出错了,那么javascript引擎会尝试寻找 then 之后的链上的rejectFunc方法。
chaining stage : chaining promises together
在处理多个异步请求时有两种实现方式
- 顺序 series
- 并行 Parallel
同步代码总是按顺序执行的,但是异步代码可以顺序,也可以并行执行。
看看下面这段代码有什么问题?
image.pnganswer: 我们无法预测请求返回的顺序,所以我们不知道哪一个getJson promise会先resolve, 就是说他们resovle 的顺序和他们被创建的顺序可能会不一样。这样的话,thumbnails方法的创建就会是随机的。当然,这并不是一个错误。但它引出了一个问题,我们怎么样才能按我们所期望的顺序来创建这些thumbnails方法?
创建一个按顺序执行的promise串(利用数组方法,不再手工创建promise 串)
思路:
解决方法一:
image.png但是这样做有问题吗?
good news: thumbnails按顺序执行
bad news: 同步执行,时间叠加
解决方法二:
image.png执行时间:
image.png问题:不能保证请求顺序
解决方法三:不用sequence,使用map
image.png