一、promise对象
1.创建promise时,它既不是成功也不是失败状态。这个状态叫作pending(待定)。
2.当promise返回时,称为 resolved(已解决).
(1). 一个成功**resolved**的promise称为**fullfilled**(**实现**)。它返回一个值,可以通过将`.then()`块链接到promise链的末尾来访问该值。`.then()`块中的执行程序函数将包含promise的返回值。
(2). 一个不成功**resolved**的promise被称为**rejected**(**拒绝**)了。它返回一个原因(**reason**),一条错误消息,说明为什么拒绝promise。可以通过将`.catch()`块链接到promise链的末尾来访问此原因。
3.Promise是一个对象,代表了一个异步操作的最终完成或者失败。
4..then()块中的回调(称为执行程序)仅在promise调用完成时运行并返回一个promise对象
二、promise的一些方法
1.Promise.all()
//Promise.all()方法传入的参数是promise对象的数组
Promise.all([a, b, c]).then(values => {
...
});
a,b,c 分别代表三个不同的promise对象,如果它们都实现,那么链接的.then()
块的执行器函数将被传递一个包含所有这些结果作为参数的数组。如果传递给Promise.all()
的任何promise都拒绝,整个块将拒绝
eg:
let a = fetch(url1);
let b = fetch(url2);
let c = fetch(url3);
Promise.all([a, b, c]).then(values => {
... //只有当所有的promise对象都变成resolved状态的时候才会执行这里的代码
});
一个简单的例子:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>fetch() promise.all() example</title>
</head>
<body>
<script>
//返回想要的promise对象(text, blob)
function fetchAndDecode(url, type){
return fetch(url).then(response => {
if(type === 'blob'){
return response.blob();
}else if(type === 'text'){
return response.text();
}
})
.catch(e => {
console.log(`There has been a problem with your fetch operation for resource "${url}": ` + e.message);
});
}
let coffee = fetchAndDecode('coffee.jpg', 'blob');
let tea = fetchAndDecode('tea.jpeg', 'blob');
let description = fetchAndDecode('description.txt', 'text');
Promise.all([coffee, tea, description]).then(values => {
console.log(values);
let objectURL1 = URL.createObjectURL(values[0]);
let objectURL2 = URL.createObjectURL(values[1]);
let descText = values[2];
// Display the images in <img> elements
let image1 = document.createElement('img');
let image2 = document.createElement('img');
image1.src = objectURL1;
image2.src = objectURL2;
document.body.appendChild(image1);
document.body.appendChild(image2);
// Display the text in a paragraph
let para = document.createElement('p');
para.textContent = descText;
document.body.appendChild(para);
});
</script>
</body>
</html>
2.promise.finally()
它可以链接到常规promise链的末尾,允许您减少代码重复并更优雅地执行操作。
原来的代码:
myPromise
.then(response => {
doSomething(response);
//无论是在then里面还是catch里面都需要执行这一步
runFinalCode();
})
.catch(e => {
returnError(e);
runFinalCode();
});
使用promise.finally()之后的代码:
myPromise
.then(response => {
doSomething(response);
})
.catch(e => {
returnError(e);
})
.finally(() => {
runFinalCode();
});
3.Promise.resolve(value)
通常而言,如果你不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。
4.使用Promise构造函数
(1)接受一个自定义的promise
eg:
let timeoutPromise = new Promise((resolve, reject) => {
//setTimeout()伪造异步操作
setTimeout(function(){
//resolve()和reject()是调用以实现或拒绝新创建的promise的函数
resolve('Success!');
}, 2000);
});
(2)拒绝一个自定义的promise
我们可以创建一个reject()
方法拒绝promise - 就像resolve()
一样,这需要一个值,但在这种情况下,它是拒绝的原因,即将传递给.catch()
的错误块。
eg:
function timeoutPromise(message, interval) {
return new Promise((resolve, reject) => {
if (message === '' || typeof message !== 'string') {
reject('Message is empty or not a string');
} else if (interval < 0 || typeof interval !== 'number') {
reject('Interval is negative or not a number');
} else {
setTimeout(function(){
resolve(message);
}, interval);
}
});
};
更加通用的例子:
function promisifyRequest(request) {
return new Promise(function(resolve, reject) {
request.onsuccess = function() {
resolve(request.result);
};
request.onerror = function() {
reject(request.error);
};
});
}
5.总结:
当我们不知道函数的返回值或返回需要多长时间时,Promises是构建异步应用程序的好方法。它们使得在没有深度嵌套回调的情况下更容易表达和推理异步操作序列,并且它们支持类似于同步try ... catch
语句的错误处理方式。
三、async和await
1.使用方法:
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
const result = await resolveAfter2Seconds();
console.log(result);
// expected output: 'resolved'
}
asyncCall();
2.异步函数需要使用前缀async,返回的是一个promise对象。
3.await关键字:使JavaScript运行时暂停此行上的代码,允许其他代码在此期间执行,直到异步函数调用返回其结果。一旦完成,您的代码将继续从下一行开始执行。
4.async/await的缺陷:await关键字会阻塞其后的代码,直到promise完成,就像执行同步操作一样,它确实允许其他的任务在此期间继续执行,但您自己的代码被阻塞,这意味着您的代码可能会因为大量的await的promise相继发生而变慢。每个await都会等待前一个完成,而你实际想要的是所有的这些promises同时开始处理(就像我们没有使用`async
/await`时那样)。
eg:
async function timeTest() {
await timeoutPromise(3000);
await timeoutPromise(3000);
await timeoutPromise(3000);
}
解决办法:
async function timeTest() {
const timeoutPromise1 = timeoutPromise(3000);
const timeoutPromise2 = timeoutPromise(3000);
const timeoutPromise3 = timeoutPromise(3000);
await timeoutPromise1;
await timeoutPromise2;
await timeoutPromise3;
}
通过将promise对象存储在变量中来同时开始它们,然后等待它们全部执行完毕。
5.async/await的类方法:我们可以在类/对象方法前面添加async
,以使它们返回promises,并await
它们内部的promises。
6.注意:return foo和 return await foo的区别(foo是一个promise)
(1)return foo:不管foo是resolved还是rejected,都会直接返回[object Promise],因为这里的return foo并没有等待foo完成就去执行了,此时foo并没有执行resolve和reject函数(个人理解)
(2)return await foo:先将异步函数挂起,等待foo的执行或拒绝,如果是拒绝,将会在返回前抛出异常,如果是执行,则返回的是传递给resolve函数的参数值。
四、简单讲一下回调地狱
- 我们都知道JavaScript是单线程的,同时由于JavaScript在设计之是用于浏览器的GUI编程,这也就需要线程不能被阻塞。
- 简单来说,异步编程就是在执行一个指令之后不是马上得到结果,而是继续执行后面的指令,等到特定的事件触发后,才得到结果。
- 异步实现的几种方式:回调、promise、Generator、await/async、事件监听、发布/订阅(参考阮大神的博客文章记录一下后面两种方法)
- 首先看一段代码:
connection.query(sql, (err, result) => {
if(err) {
console.err(err)
} else {
connection.query(sql, (err, result) => {
if(err) {
console.err(err)
} else {
...
}
})
}
})
再看一段代码:node.js
fs.readdir(path.join(_dirname,'..'),function(err , files){ files.forEach(function(filename , index){ fs.readFle(filename , function(){ .... }) }) })
我们都知道异步操作并不是一执行就能马上返回结果,异步操作在执行的过程中我们可以去执行接下来的主线程的函数栈,所以我们并不会去跟踪异步操作的执行过程,我们只需要知道这个异步操作完成(成功/失败)之后需要执行什么操作,所以就需要在异步操作完成之后调用回调函数。看上面的代码,如果我的异步操作并不是同时进行而是一个接着一个触发,那我每一个异步操作完成之后都需要调用回调函数,回调函数不断嵌套,这样就会陷进回调地狱中去。
- 回调陷阱的坏处:
不利于代码阅读和维护,各个部分之间高度耦合,流程会很混乱,而且每个任务只能指定一个回调函数。
五、异步实现的几种方式:
1.回调函数
总所周知,回调函数通常以参数的形式传入主函数中,当主函数执行到某个操作时需要对该操作做额外处理的时候就会调用回调函数。
eg:
function f1(callback){
setTimeout(function () {
// f1的任务代码
callback();
}, 1000);
}
执行代码就变成了:
f1(f2);
采用这种方式,我们把同步操作变成了异步操作,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。
回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合)(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。
2.Genarator
Genarator简称生成器,可以理解为一个可以遍历的状态机(状态之间的转换),区别于for循环,不像for循环那样,需要全部都循环完毕之后才能得出结果。但是Genarator是可以由一个状态转化为下一个状态。
eg:
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 2000)
});
};
var b = co(function *() {
var val = yield a();
console.log(val)
})
b()
上面的这段代码是借助 TJ 的co实现的,依照约定,co 中 yield 后面只能跟 Thunk 或者 Promise.
3.async/await
这个上面已经详细讨论过了,这里只贴一段代码温习一下
eg:
function a(){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 2000);
})
}
var b = async function(){
var val = await a();
console.log(val);
}
这里先将一个异步函数赋值给变量b,await使得异步函数在这段代码中暂停,等待Promise对象的完成,但是可以继续执行其他的任务。等到Promise完成之后(fullfilled/rejected),再去执行异步函数后面的代码。
4.事件监听(详细请去阮大大博客上看)
还是以f1和f2为例,首先,为f1绑定一个事件(这里采用的是jQuery的写法)
f1.on('done', f2);
上面这行代码的意思是,当f1发生done事件,就执行f2。然后,对f1进行改写:
function f1(){
setTimeout(function () {
// f1的任务代码
f1.trigger('done');
}, 1000);
}
f1.trigger('done')
表示,执行完毕之后,立马触发done事件,从而开始执行f2。
这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合"(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。
5.发布/订阅
上一节的"事件",完全可以理解成"信号"。
我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。
这个模式有多种实现,下面采用的是Ben Alman的Tiny Pub/Sub,这是jQuery的一个插件。
首先,f2向"信号中心"jQuery订阅"done"信号。
jQuery.subscribe("done", f2);
然后,f1进行如下改写:
function f1(){
setTimeout(function () {
// f1的任务代码
jQuery.publish("done");
}, 1000);
}
jQuery.publish("done")
的意思是,f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发f2的执行
此外,f2完成执行后,也可以取消订阅(unsubscribe)。
jQuery.unsubscribe("done", f2);
这种方法的性质与"事件监听"类似,但是明显优于后者。因为我们可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。