相对于博客园版本更新
参考资料
目录
前置说明
ECMAScript 6 新增了正式的 Promise(期约)引用类型,支持优雅地定义和组织异步逻辑。接下来几个版本增加了使用 async 和 await 关键字定义异步函数的机制
JavaScript 是单线程事件循环模型。异步行为是为了优化因计算量大而时间长的操作,只要你不想为等待某个异步操作而阻塞线程执行,那么任何时候都可以使用
同步与异步
JS中同步任务会立即执行并放入调用栈中,而异步任务会被放入事件队列中,等待调用栈中的任务执行完毕后再被推入调用栈中执行。当异步任务被推入调用栈中执行时,它就变成了同步任务。这种机制被称为事件循环。
同步行为对应内存中顺序执行的处理器指令。每条指令都会严格按照它们出现的顺序来执行,而每条指令执行后也能立即获得存储在系统本地(如寄存器或系统内存)的信息。这样的执行流程容易分析程序在执行到代码任意位置时的状态(比如变量的值)。
例如
let x = 3;
x = x + 4;
在程序执行的每一步,都可以推断出程序的状态。这是因为后面的指令总是在前面的指令完成后才会执行。等到最后一条指定执行完毕,存储在 x 的值就立即可以使用。
异步行为不用等前面的任务执行完。类似于系统中断,即当前进程外部的实体可以触发代码执行。异步代码不容易推断。耗时长的任务应该异步执行。
例如,在定时回调中执行一次简单的数学计算:
let x = 3;
setTimeout(() => x = x + 4, 1000);
这段程序虽与上面同步代码执行的任务一样,都是把两个数加在一起,但这一次执行线程不知道 x 值何时会改变,因为这取决于回调何时从消息队列出列并执行。虽然这个例子对应的低级代码最终跟前面的例子没什么区别,但第二个指令块(加操作及赋值操作)是由系统计时器触发的,这会生成一个入队执行的中断。到底什么时候会触发这个中断,这对 JavaScript 运行时来说是一个黑盒
异步编程主要包含以下几类,异步编程传统的解决方案是回调函数,这种传统的方式容易导致“回调地狱”,即函数多层嵌套,让代码难以阅读,维护困难,空间复杂度大大增加
- fs 文件操作
require('fs').readFile('./index.html', (err,data)=>{})
- 数据库操作
- AJAX
$.get('/server', (data)=>{})
- 定时器
setTimeout(()=>{}, 2000);
Promise 对象
Promise属于ES6规范, 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。期约故意将异步行为封装起来,从而隔离外部的同步代码
- 从语法上来说: Promise 是一个构造函数
- 从功能上来说: promise 对象用来封装一个异步操作并可以获取其成功/失败的结果值
const p=new Promise(()=>{})
console.log(p);
Promise 的状态及结果
Promise 构造函数: Promise (excutor)
- executor 函数: 执行器 (resolve, reject) => {}
- executor 会在 Promise 内部立即同步调用(即executor函数直接放入调用栈中,而不是消息队列,new Promise()执行时就会立即执行executor 函数),异步操作在执行器中执行
let p = new Promise((resolve, reject) => {
// 同步调用
console.log(111);
});
console.log(222);
期约是一个有状态的对象,Promise实例对象中的属性PromiseState存储着该Promise实例的状态,可能处于如下 3 种状态之一:
- 待定(pending):期约的最初始状态,在待定状态下,期约可以落定(settled)为resolved状态或reject状态。无论落定为哪种状态都是不可逆的。只要从待定转换为解决或拒绝,期约的状态就不再改变。
例如使用一个空函数对象来应付一下解释器:
let p = new Promise(() => {});
setTimeout(console.log, 0, p); // Promise <pending>
之所以说是应付解释器,是因为如果不提供执行器函数,就会抛出 SyntaxError。
无论 resolve()和 reject()中的哪个被调用,状态转换都不可撤销了。于是继续修改状态会静默失败,如下所示:
let p = new Promise((resolve, reject) => {
resolve();
reject(); // 没有效果
});
setTimeout(console.log, 0, p); // Promise <resolved>
- 解决(resolved,有时候也称为“兑现”,fulfilled):代表成功
- 拒绝(rejected):代表失败
无论变为成功还是失败, 都会有一个结果数据(这个数据存储在Promise实例对象的PromiseResult属性中),成功的结果数据一般称为 value, 失败的结果数据一般称为 reason
期约主要有两大用途:
- 首先是抽象地表示一个异步操作。期约的状态代表期约是否完成。“待定”表示尚未开始或者正在执行中。“解决”表示已经成功完成,而“拒绝”则表示没有成功完成
- 在另外一些情况下,期约封装的异步操作会实际生成某个值,而程序期待期约状态改变时可以访问这个值。相应地,如果期约被拒绝,程序就会期待期约状态改变时可以拿到拒绝的理由。
为了支持这两种用例,每个期约只要状态切换为解决,就会有一个私有的内部值(value)。类似地,每个期约只要状态切换为拒绝,就会有一个私有的内部理由(reason)。无论是值还是理由,都是包含原始值或对象的不可修改的引用。二者都是可选的,而且默认值为 undefined。在期约到达某个落定状态时执行的异步代码始终会收到这个值或理由。
由于期约的状态是私有的,所以只能在内部进行操作。内部操作在期约的执行器函数中完成。执行器函数主要有两项职责:初始化期约的异步行为和控制状态的最终转换。其中,控制期约状态的转换是通过调用它的两个函数参数实现的。这两个函数参数通常都命名为 resolve()和 reject()。调用resolve()会把状态切换为兑现,调用 reject()会把状态切换为拒绝。另外,调用 reject()也会抛出错误
let p1 = new Promise((resolve, reject) => resolve());
setTimeout(console.log, 0, p1); // Promise <resolved>
let p2 = new Promise((resolve, reject) => reject());
setTimeout(console.log, 0, p2); // Promise <rejected>
// Uncaught error (in promise)
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理
有了Promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise
对象提供统一的接口,使得控制异步操作更加容易
Promise方法
Promise.prototype.then()
Promise.prototype.then([onResolved|null],[onRejected|null])是为期约实例添加处理程序的主要方法。
参数:onResolved 处理程序和 onRejected 处理程序。这两个参数都是可选的,如果提供的话,则会在期约分别进入“兑现”和“拒绝”状态时执行。
返回值:一个新的Promise对象
可以指定多个回调,当promise对象变为相应的状态的时候就都会执行
let p = new Promise((resolve, reject) => {
resolve('OK');
});
///指定回调 - 1
p.then(value => {
console.log(value);
});
//指定回调 - 2
p.then(value => {
alert(value);
});
问:then()返回的Promise对象的状态是如何决定的呢?
① 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
console.log(value);
//1. 抛出错误
throw '出了问题';
}, reason => {
console.warn(reason);
});
② 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
console.log(value);
//2. 返回结果是非 Promise 类型的对象
return 521;
}, reason => {
console.warn(reason);
});
console.log(result);
③ 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
console.log(value);
//3. 返回结果是 Promise 对象
return new Promise((resolve, reject) => {
// resolve('success');
reject('error');
});
}, reason => {
console.warn(reason);
});
console.log(result);
不管调用的是onResolved还是onRejected函数,返回的新promise对象主要由返回值决定(抛出错误的情况除外)
then()的链式调用
问:promise 如何串连多个操作任务?
(1) promise 的 then()返回一个新的 promise, 可以形成 then()的链式调用
(2) 通过 then 的链式调用串连多个同步/异步任务
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
const result=p.then(value => {
return new Promise((resolve, reject) => {
resolve("success");
});
}).then(value => {
console.log(value);
}).then(value => {
console.log(value);
})
console.log(result);
p.then(value => {
return new Promise((resolve, reject) => {
resolve(“success”);
});
}) // Promise (resolved): ‘success’
p.then(value => {
return new Promise((resolve, reject) => {
resolve(“success”);
});
}).then(value => {
console.log(value);
})// Promise (resolved): undefined 因为没有返回值也没有抛出错误,所以值是undefined, 因为undefined不是Promise对象,所以返回的Promise状态为resolved
p.then(value => {
return new Promise((resolve, reject) => {
resolve(“success”);
});
}).then(value => {
console.log(value);
}).then(value => {
console.log(value);
})// Promise (resolved): undefined
中断then()链
方法:在then()的回调函数中返回一个 pendding 状态的 promise 对象。因为pendding状态的promise对象不会触发onResolved()或onRejected()函数
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
console.log(111);
//有且只有一个方式
return new Promise(() => {});//
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
穿透:当没有指定相应Promise状态的回调函数时,就可以跳过执行该then()
练习:分析输出结果
1.
let p = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('OK');
reject('Err');
}, 10);
});
p.then(null,reason => {
console.log(111);
}).then(null,reason => {
console.log(222);
}).then(null,reason => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
分析:p.then(null,reason => {
console.log(111);
})的返回值是Promise (resolved):undefined, 后面没有对应的onResolved的回调函数,就跳过了执行,所以控制台只输出了111
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
// reject('Err');
}, 10);
});
// console.log(p.then(value => {
// console.log(111);
// },reason=>{
// throw '失败啦!';
// })==p.then(value => {
// console.log(111);
// },reason=>{
// throw '失败啦!';
// }).then(value => {
// console.log(222);
// })); // false
console.log(p.then(value => {
console.log(111);
},reason=>{
throw '失败啦!';
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}));
p.then(value => {
throw '失败啦!';
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
p.then(value => {
throw ‘失败啦!’;
})返回Promise (rejected): ‘失败啦!’,后面没有对应的onRejected()回调函数,就跳过了执行,所以就直接执行catch()来捕获错误信息
Promise.prototype.catch()
Promise.prototype.catch(onRejected)
catch()基于then()做了一个单独的封装,只接收rejected状态的回调函数
给Promise对象设置回调函数的方法有Promise.prototype.then()和Promise.prototype.catch(),注意Promise.prototype.catch()只能指定错误的回调函数
Promise.prototype.finally()
Promise.prototype.finally(onFinally)
Promise 实例的 finally() 方法用于注册一个在 promise 敲定(兑现或拒绝)时调用的函数。它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 方法。
promise状态为fulfilled/resolved或rejected都会执行onFinally回调,如果then和catch中有共同的代码,或不论promise是成功还是失败都会执行的操作,可以将这部分代码放到finally中,这可以让你避免在 promise 的 then() 和 catch() 处理器中重复编写代码。
示例如下
function checkMail() {
return new Promise((resolve, reject) => {
if (Math.random() > 0.5) {
resolve('Mail has arrived');
} else {
reject(new Error('Failed to arrive'));
}
});
}
checkMail()
.then((mail) => {
console.log(mail);
})
.catch((err) => {
console.error(err);
})
.finally(() => {
console.log('Experiment completed');
});
// 如果不用finally,then和catch中都要写console.log('Experiment completed');
/*
checkMail()
.then((mail) => {
console.log(mail);
console.log('Experiment completed');
})
.catch((err) => {
console.error(err);
console.log('Experiment completed');
})
*/
Promise.resolve()
静态方法,不是实例方法
期约并非一开始就必须处于待定状态,然后通过执行器函数才能转换为落定状态。通过调用Promise.resolve()静态方法,可以实例化一个解决的期约。
下面两个期约实例实际上是一样的:
let p1 = new Promise((resolve, reject) => resolve());
let p2 = Promise.resolve();
参数:成功的数据或 promise 对象
说明: 返回一个成功/失败的 promise 对象
- 参数为非Promise对象,则返回一个成功的Promise对象,成功的结果为该参数
let p1 = Promise.resolve(521);
console.log(p1);
let p = Promise.resolve(new Error('foo'));
setTimeout(console.log, 0, p); // Promise <resolved>: Error: foo
- 参数为Promise对象时,则返回该Promise对象
对这个静态方法而言,如果传入的参数本身是一个期约,那它的行为就类似于一个空包装。因此,Promise.resolve()可以说是一个幂等方法
let p = Promise.resolve(7);
setTimeout(console.log, 0, p === Promise.resolve(p)); // true
setTimeout(console.log, 0, p === Promise.resolve(Promise.resolve(p))); // true
// 这个幂等性会保留传入期约的状态:
let p = new Promise(() => {});
setTimeout(console.log, 0, p); // Promise <pending>
setTimeout(console.log, 0, Promise.resolve(p)); // Promise <pending>
setTimeout(console.log, 0, p === Promise.resolve(p)); // true
let p2 = Promise.resolve(new Promise((resolve, reject) => {
resolve('OK');
}));
console.log(p2);
p2.catch(reason => {
console.log(reason);
})
let p2 = Promise.resolve(new Promise((resolve, reject) => {
reject('Error');
}));
console.log(p2);
p2.catch(reason => {
console.log(reason);
})
Promise.reject()
与 Promise.resolve()类似,Promise.reject()会实例化一个拒绝的期约并抛出一个异步错误(这个错误不能通过 try/catch 捕获,而只能通过拒绝处理程序捕获)。
下面的两个期约实例实际上是一样的:
let p1 = new Promise((resolve, reject) => reject());
let p2 = Promise.reject();
参数:失败的理由或 promise 对象
说明: 返回一个失败的 promise 对象
- 参数为非Promise对象时,返回的失败Promise对象的理由就为该参数值
let p1 = Promise.reject(521);
console.log(p1);
- 参数为Promise对象时,返回的失败Promise对象的理由就为该参数值(即失败的理由就是该Promise对象)
let p2 = Promise.reject(new Promise((resolve, reject) => {
resolve('OK');
}));
console.log(p2);
Promise.all()
参数: 包含 n 个 promise 的数组
返回值:Promise对象。只有所有的 promise 都fulfilled该promise才fulfilled, fulfilled值为所有promise成功值组成的数组(其元素顺序与传入的 promise 一致,而非按照兑现的时间顺序排列)。只要有一个promise状态为rejected该promise就rejected,失败原因为第一个失败的promise的失败原因
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
const result = Promise.all([p1, p2, p3]);
console.log(result);
- 当有失败的promise时该promise就失败,失败理由为第一个失败的promise的失败理由
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.reject('Error1');
let p3 = Promise.reject('Error2')
const result = Promise.all([p1, p2, p3]);
console.log(result);
Promise.all() 方法是 promise 并发方法之一。它可用于聚合多个 Promise 的结果。通常在 我们希望代码继续执行之前完成这些异步任务,整个代码依赖于这些任务成功完成时使用。
亲身经历的一道面试题,来自数字马力
面试题:假如有3个请求A, B, C,请求获取到数据所花费的时间分别为1,2,3秒。想要在这3个请求都成功后才将它们获取的数据展示到页面上,你会怎么做?
我:创建Pomise对象p1,p2,p3,将这3个请求分别写到p1,p2,p3的Promise的函数参数中,请求成功后用resolve标记,失败用reject标记,然后将p1,p2,p3组成的数组作为Promise.all的参数,再在Promise.all的then中进行页面展示数据的操作
面试官:用Promise.all的话这3个请求总共耗时多久?
答案:3s,因为这3个请求在Promise.all中是并发执行的
这里用代码模拟一下
console.time('p') // 启动一个计时器来跟踪某一个操作的占用时长,每一个计时器必须拥有唯一的
名字,p是计时器的名字
let p1=new Promise((resolve, reject) => {
setTimeout(()=>{ // 这里用定时模拟请求的耗时
resolve('p1')
},1000)
})
let p2=new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('p2')
},2000)
})
let p3=new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('p3')
},3000)
})
Promise.all([p1,p2,p3]).then((data)=>{
console.timeEnd('p') // 停止一个通过 console.time() 启动的计时器,输出对应计时器所
经过的毫秒数
console.log(data);
})
确实是3秒(因为其他代码执行也会占用一些时间,实际会比3秒多一点,可以忽略不计)
Promise 类提供了四个静态方法来促进异步任务的并发:
- Promise.all():在所有传入的 Promise 都被兑现时兑现;在任意一个 Promise 被拒绝时拒绝。
- Promise.allSettled():与Promise.all()相似,区别就是即使确定有Promise被拒绝时也不会立即rejected,会等所有的 Promise 都敲定。
- Promise.any():在任意一个 Promise 被兑现时兑现;仅在所有的 Promise 都被拒绝时才会拒绝。
- Promise.race():在任意一个 Promise 被敲定时敲定。换句话说,在任意一个 Promise 被兑现时兑现;在任意一个的 Promise 被拒绝时拒绝。
Promise.any()
Promise.race()
通过这种方式,可以检测页面中某个请求是否超时,并输出相关的提示信息。
参数: 包含 n 个 promise 的数组
说明: 返回一个新的 promise, 该promise等于第一个完成的 promise(即第一个确定状态的promise)
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
//调用
const result = Promise.race([p1, p2, p3]);
console.log(result);
改变Promise对象状态的方式
- promise回调函数中改变
let p = new Promise((resolve, reject) => {
//1. resolve 函数
// resolve('ok'); // pending => fulfilled (resolved)
//2. reject 函数
// reject("error");// pending => rejected
//3. 抛出错误
// throw '出问题了';// pending => rejected
});
console.log(p);
2. 调用实例方法Promise.resolve()或Promise.reject()
基本用法
ES6 规定,Promise
对象是一个构造函数,用来生成Promise
实例
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署
Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
下面是一个抽奖示例
<body>
<button id="btn">点击抽奖</button>
<script>
const btn=document.getElementById("btn")
btn.onclick=function(){
// 每次点击按钮创建一个Promise实例对象
const p =new Promise((resolve,reject)=>{
setTimeout(()=>{
let n =parseInt(Math.random()*101)//取值[1,100]的整数
if(n<30){
resolve(n)
}else{
reject(n)
}
},10)
})
p.then((value)=>{
alert("恭喜中奖!中奖数字为"+value);
},(reason)=>{
alert("再接再厉! 中奖数字为"+reason);
})
}
</script>
</body>
加载图片资源例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div></div>
<script>
function loadImageAsync(url) {
var promise = 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;
});
return promise;
}
loadImageAsync("http://iwenwiki.com/api/vue-data/vue-data-1.png")
.then(function(data){
console.log(data);
$("div").append(data)
},function(error){
$("div").html(error)
})
</script>
</body>
</html>
实时效果反馈
1. Promise
的作用是什么,下列描述正确的是:
A Promise
是异步编程的一种解决方案,可以将异步操作以同步操作的流程表达出来
B Promise
是同步编程的一种解决方案,可以将同步操作以异步操作的流程表达出来
C Promise
使得控制同步操作更加容易
D Promise
还不是ES6的标准,目前是社区版本
答案
1=>A
Promise对象_Ajax实操
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1WqqgNyM-1688905076062)(imgs/image-20220214152654593.png)]
Promise封装Ajax,让网络请求的异步操作变得更简单
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
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("http://iwenwiki.com/api/blueberrypai/getIndexBanner.php").then(function (json) {
console.log(json);
}, function (error) {
console.error('出错了', error);
});
</script>
</body>
</html>
util.promisefy()函数
传入一个遵循常见的错误优先的回调风格的函数(即以(err, value)=>{…}回调作为最后一个参数),并返回一个返回值为promise对象的函数版本
“错误优先”是指回调函数中,错误参数为回调函数的第一个参数。node.js环境中fs模块中的异步api大多是这种风格的
参数:函数
返回值:函数,返回的该函数的返回值是promise对象
将函数promise化的好处:函数promise化之后,函数的返回值就变成了promise对象,这样就可以调用promise的实例方法then()或catch(), 利用它们的链式写法,就能避免回调函数多层嵌套
示例:调用util.promisefy()函数,将fs.readFile()函数promise化
//引入 util 模块
const util = require('util');
//引入 fs 模块
const fs = require('fs');
//promise化fs.readFile()函数
let mineReadFile = util.promisify(fs.readFile);
// promise化之后就可以调用promise实例的then()或catch()方法
mineReadFile('./resource/content.txt').then(value=>{
console.log(value.toString());
});
封装promisefy函数
在实际应用中,一个函数满足这几个条件,就可以被 promisify 化:
- 该方法必须包含回调函数
- 回调函数必须执行
- 回到函数第一个参数代表 err 信息,第二个参数代表成功返回的结果
// promisefy()返回一个函数,返回的该函数的返回值是Promise对象
// fn指要promise化的函数
const promisefy = (fn) => {
// ‘...args’表示剩余参数
return function(...args){
return new Promise((resolve,reject)=>{
fn(...args,(err,data)=>{
if(err) reject(err);
resolve(data)
})
})
}
}
实例(第14届蓝桥杯省赛第三期模拟题)
题目
下面就请你以 Node.js 中常用的读取文件操作为例,封装一个 Promisefy 函数,将回调形式调用的读取文件方法转换成一个 Promise 的版本。
目录结构如下:
- index.js 是需要补充代码的 js 文件。
- test.md 供读取的示例文件。
请在 index.js 文件中的补全代码,完成 promisefy 函数的封装。将 fs 中的 readFile 方法 promise 化。也就是说 readFileSync 方法执行后,会返回一个 promise,可以调用 then 方法执行成功的回调或失败的回调。
在控制台运行:node index
, 此时应打印出 true,即:回调形式的 fs.readFile 方法读取同个文件的结果与 Promise 形式读取结果一致。
参考答案
const fs = require('fs')
const path = require('path')
const textPath = path.join(__dirname, '/test.md')
// 读取示例文件
fs.readFile(textPath, 'utf8', (err, contrast) => {
// 通过promisefy转化为链式调用
const readFileSync = promisefy(fs.readFile)
readFileSync(textPath, 'utf8')
.then((res) => {
console.log(res === contrast) // 此处结果预期:true,即promise返回内容与前面读取内容一致
})
.catch((err) => {})
})
const promisefy = (fn) => {
// TODO 此处完成该函数的封装
// ‘...args’表示剩余参数
return function(...args){
return new Promise((resolve,reject)=>{
fn(...args,(err,data)=>{
if(err) reject(err);
resolve(data)
})
})
}
}
module.exports = promisefy // 请勿删除该行代码
封装Promise实战
ES5写法
// ES5写法
//声明构造函数
function Promise(executor){
//添加属性
this.PromiseState = 'pending';
this.PromiseResult = null;
//声明属性
this.callbacks = [];
//保存实例对象的 this 的值
const self = this;// self _this that
//resolve 函数
function resolve(data){
//判断状态
if(self.PromiseState !== 'pending') return;
//1. 修改对象的状态 (promiseState)
self.PromiseState = 'fulfilled';// resolved
//2. 设置对象结果值 (promiseResult)
self.PromiseResult = data;
//调用成功的回调函数
setTimeout(() => {
self.callbacks.forEach(item => {
item.onResolved(data);
});
});
}
//reject 函数
function reject(data){
//判断状态
if(self.PromiseState !== 'pending') return;
//1. 修改对象的状态 (promiseState)
self.PromiseState = 'rejected';//
//2. 设置对象结果值 (promiseResult)
self.PromiseResult = data;
//执行失败的回调
setTimeout(() => {
self.callbacks.forEach(item => {
item.onRejected(data);
});
});
}
try{
//同步调用『执行器函数』
executor(resolve, reject);
}catch(e){
//修改 promise 对象状态为『失败』
reject(e);
}
}
//添加 then 方法
Promise.prototype.then = function(onResolved, onRejected){
const self = this;
//判断回调函数参数
if(typeof onRejected !== 'function'){
onRejected = reason => {
throw reason;
}
}
if(typeof onResolved !== 'function'){
onResolved = value => value;
//value => { return value};
}
return new Promise((resolve, reject) => {
//封装函数
function callback(type){
try{
//获取回调函数的执行结果
let result = type(self.PromiseResult);
//判断
if(result instanceof Promise){
//如果是 Promise 类型的对象
result.then(v => {
resolve(v);
}, r=>{
reject(r);
})
}else{
//结果的对象状态为『成功』
resolve(result);
}
}catch(e){
reject(e);
}
}
//调用回调函数 PromiseState
if(this.PromiseState === 'fulfilled'){
setTimeout(() => {
callback(onResolved);
});
}
if(this.PromiseState === 'rejected'){
setTimeout(() => {
callback(onRejected);
});
}
//判断 pending 状态
if(this.PromiseState === 'pending'){
//保存回调函数
this.callbacks.push({
onResolved: function(){
callback(onResolved);
},
onRejected: function(){
callback(onRejected);
}
});
}
})
}
//添加 catch 方法
Promise.prototype.catch = function(onRejected){
return this.then(undefined, onRejected);
}
//添加 resolve 方法
Promise.resolve = function(value){
//返回promise对象
return new Promise((resolve, reject) => {
if(value instanceof Promise){
value.then(v=>{
resolve(v);
}, r=>{
reject(r);
})
}else{
//状态设置为成功
resolve(value);
}
});
}
Date.now()
//添加 reject 方法
Promise.reject = function(reason){
return new Promise((resolve, reject)=>{
reject(reason);
});
}
//添加 all 方法
Promise.all = function(promises){
//返回结果为promise对象
return new Promise((resolve, reject) => {
//声明变量
let count = 0;
let arr = [];
//遍历
for(let i=0;i<promises.length;i++){
//
promises[i].then(v => {
//得知对象的状态是成功
//每个promise对象 都成功
count++;
//将当前promise对象成功的结果 存入到数组中
arr[i] = v;
//判断
if(count === promises.length){
//修改状态
resolve(arr);
}
}, r => {
reject(r);
});
}
});
}
//添加 race 方法
Promise.race = function(promises){
return new Promise((resolve, reject) => {
for(let i=0;i<promises.length;i++){
promises[i].then(v => {
//修改返回对象的状态为 『成功』
resolve(v);
},r=>{
//修改返回对象的状态为 『失败』
reject(r);
})
}
});
}
后续再补充finally封装
点击查看代码ES6写法// ES6写法
// 在异步任务中不能抛出错误,即使是内置的Promise也捕获不到
class Promise {
constructor(executor) {
// 设置默认的PromiseState和PromiseResult
// 因为Promise()构造函数是以实例的方式调用(new运算符),所以this指向实例
this.PromiseState = 'pendding'
this.PromiseResult = null
// 将callback对象定义到实例内部,用来存储后面通过then()或catch()指定的onResolved()和onRejected()函数,这是考虑到executor中通过异步任务改变promise实例状态和多个then()指定了多个onResolved()或onRejected()回调的情况
this.callbacks = [];
// 而resolve()和reject()是以函数形式调用的,this为window对象,所以这里用self存储指向实例的this
const self = this;
function resolve(data) {
// 该判断为了避免状态重复改变
if (self.PromiseState !== 'pendding') return;
self.PromiseState = 'fulfilled'//或'resolved'
self.PromiseResult = data
// 注意要在Promise实例状态改变并且数据赋值之后调用onResolved
// 注意在调用前要判断有没有定义onResolved()函数,所以就看callbacks数组第一个元素有没有onResolved属性
// if(self.callbacks[0].onResolved){
// 通过setTimeout将then中的回调设置成异步, 因为内置的Promise的实例方法then()中的回调是异步的
setTimeout(() => {
self.callbacks.forEach(item => {
item.onResolved(data)
})
});
// }
};
function reject(data) {
// 该判断为了避免状态重复改变
if (self.PromiseState !== 'pendding') return;
self.PromiseState = 'rejected'
self.PromiseResult = data
// 注意要在Promise实例状态改变并且数据赋值之后调用onRejected
// 注意在调用前要判断有没有定义onRejected()函数,所以就看callbacks数组第一个元素有没有onRejected属性
// if(self.callbacks[0].onRejected){
// 通过setTimeout将then中的回调设置成异步, 因为内置的Promise的实例方法then()中的回调是异步的
setTimeout(() => {
self.callbacks.forEach(item => {
item.onRejected(data)
})
})
// }
};
try {
// executor在传入时就能确定是个函数,所以这里不需要再定义executor, 只是调用即可
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
//添加 then 方法
then(onResolved, onRejected) {
const self = this
// 考虑异常穿透情况
if (typeof onRejected !== 'function') {
onRejected = reason => {
throw reason;
}
}
if (typeof onResolved !== 'function') {
onResolved = value => value // (value)=>{return value}的简写形式
}
//
return new Promise((resolve, reject) => {
// 这个callback()函数得放到返回的Promise实例里,否则会报错,说resolve和reject未定义
function callback(type) {
try {
// 因为callback()是以函数形式调用的,此时this指向window对象,所以此处参数不能为this.PromiseResult
let result = type(self.PromiseResult)
if (result instanceof Promise) {
result.then(v => {
resolve(v)
}, e => {
reject(e)
})
} else {// 抛出错误时result是undefined还是null? 不进入该分支应该
resolve(result)
}
} catch (e) {//考虑抛出错误的情况
reject(e)
}
}
// 考虑executor中同步任务改变promise实例状态
if (this.PromiseState === 'fulfilled') {
// 通过setTimeout将then中的回调设置成异步, 因为内置的Promise的实例方法then()中的回调是异步的
setTimeout(() => {
callback(onResolved)
})
}
if (this.PromiseState === 'rejected') {
// 通过setTimeout将then中的回调设置成异步, 因为内置的Promise的实例方法then()中的回调是异步的
setTimeout(() => {
callback(onRejected)
})
}
//
// 考虑executor中异步任务改变promise实例状态
if (this.PromiseState === 'pendding') {
this.callbacks.push({
onResolved: function () {
callback(onResolved)
},
onRejected: function () {
callback(onRejected)
}
})
}
})
}
// 定义Promise.prototype.catch()
catch(onRejected) {
return this.then(undefined, onRejected)
}
// 定义Promise.resolve()静态方法,注意静态方法要加关键词static
static resolve(value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(
v => resolve(v),
e => reject(e))
} else {
resolve(value)
}
})
}
// 定义Promise.reject()静态方法
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
// 定义Promise.all()静态方法
static all(promises) {
return new Promise((resolve, reject) => {
let arr = []
let count = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].then(v => {
// 注意不要写成arr.push(v),否则先敲定状态的promise就会跑到前面,而不是按数组元素的顺序
arr[i] = v;// 这种写法就保证了按照promises数组元素的顺序保存pomise状态
count++;
if (count === promises.length) {
resolve(arr)
}
}, r => {
reject(r)
})
}
// 因为要promises中的Promise对象状态敲定时才触发判断,如果这个判断放在这,那只会在调用Promise.all()时触发一次,这时初始的count不等于promises.length(promises非空的情况下),导致Promise.all返回数据为空数组的promise对象
// if(count===promises.length){
// resolve(arr)
// }
})
}
// 定义Promise.allSettled()静态方法
static allSettled(promises) {
return new Promise((resolve, reject) => {
let arr = []
let count = 0
for (let i in promises) {
promises[i].then((v) => {
// 注意不要写成arr.push({status: 'fulfilled',value: v}),否则先敲定状态的promise就会跑到前面,而不是按promises数组元素的顺序
arr[i] = { // 这种写法就保证了按照传入allSettled的数组元素的顺序保存pomise状态
status: 'fulfilled',
value: v
}
count++
if (count === promises.length) {
resolve(arr)
}
}, (e) => {
arr[i] = {
status: 'rejected',
reason: e
}
count++
if (count === promises.length) {
resolve(arr)
}
})
}
})
}
// 定义Promise.any()静态方法
static any(promises) {
return new Promise((resolve, reject) => {
let arr = []
let count = 0
for (let i in promises) {
promises[i].then(v => {
resolve(v)
}, e => {
arr[i] = e
count++
if (count === promises.length) {
reject(arr)
}
})
}
})
}
// 定义Promise.race()静态方法
static race(promises) {
return new Promise((resolve, reject) => {
for (let i in promises) {
promises[i].then(
v => resolve(v),
r => reject(r))
}
})
}
}
测试代码如下
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p1')
}, 100)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p2')
}, 0)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p3')
}, 800)
})
console.log(Promise.any([p1, p2, p3]));
console.log(Promise.race([p1, p2, p3]));
console.log(Promise.all([p1, p2, p3]));
Async 函数
async和await是基于Promise的语法糖(语法糖是一种简化后的代码写法,它能方便程序员的代码开发))
async 英文单词的意思是异步,虽然它是 ES8 中新增加的一个关键字,async 通常写在一个函数的前面,表示这是一个异步请求的函数,将返回一个 Promise 对象,并可以通过 then 方法取到函数中的返回值
使用 async 关键字可以让函数具有异步特征,但总体上其代码仍然是同步求值的。而在参数或闭包方面,异步函数仍然具有普通 JavaScript 函数的正常行为。异步函数的返回值会被 Promise.resolve()包装成一个期约对象。异步函数始终返回期约对象。
ES2017 标准引入了 async 函数,使得异步操作变得更加方便
async函数可以将异步操作变为同步操作
- 函数的返回值为 promise 对象
- promise 对象的结果由 async 函数执行的返回值决定
- 如果返回值是一个非Promise类型的数据,相当于执行了Promise.resolve(), 则返回的Promise实例状态为fulfilled
- 如果返回值是一个Promise对象,相当于执行了Promise.resolve(返回值),则返回的Promise实例等效于该Promise对象
async function main(){
// return 521; // Promise<fulfilled>:521
return new Promise((resolve, reject) => {
// resolve('OK'); // Promise<fulfilled>:'OK'
reject('Error'); // Promise<rejected>:'Error'
});
//3. 抛出异常
// throw "Oh NO"; // Promise<rejected>:"Oh NO"
}
let result = main();
console.log(result);
示例代码
function print(){
setTimeout(() =>{
console.log("定时器");
},1000)
console.log("Hello");
}
print()
基本语法
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
异步应用
function ajax(url){
return new Promise(function(resolve,reject){
$.getJSON(url,function(result){
resolve(result)
},function(error){
reject(error)
})
})
}
async function getInfo(){
let ids = await ajax("http://iwenwiki.com/api/generator/list.php")
let names = await ajax("http://iwenwiki.com/api/generator/id.php?id="+ids[0])
let infos = await ajax("http://iwenwiki.com/api/generator/name.php?name=" + names.name)
console.log(infos);
}
getInfo();
实时效果反馈
1. Async
是什么:
A Async是完成网络请求,如Ajax一样
B Async的作用是完成异步网络请求,如Ajax一样
C Async使得异步操作变得更加方便
D Async是新的网络请求解决方案
答案
1=>C
await 表达式
await 可以理解为 async await 的简写,表示等待异步执行完成。await 后可以返回任意的表达式,如果是正常内容,则直接执行,如果是异步请求,必须等待请求完成后,才会执行下面的代码
- await 右侧的表达式结果一般为 promise 对象, 但也可以是其它的值
- 如果表达式是 promise 对象, await 返回的是 promise 成功的值, promise对象状态是rejected时,需要捕获错误来查看错误理由
- 如果表达式是其它值, 直接将此值作为 await 的返回值
注意
- await 必须写在 async 函数中, 但 async 函数中可以没有 await
- 如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try…catch 捕获处理
// 函数 p 返回的是一个 Promise 对象,在对象中,延时 2 秒,执行成功回调函数,相当于模拟一次异步请求
function p(v) {
return new Promise(function (resolve) {
setTimeout(function () {
// 在 p 函数执行时,将函数的实参值 v ,作为执行成功回调函数的返回值。
resolve(v);
}, 2000);
});
}
// 一个用于正常输出内容的函数
function log() {
console.log("2.正在操作");
}
async function fn() {
console.log("1.开始");
await log();
let p1 = await p("3.异步请求");
console.log(p1);
console.log("4.结束");
}
fn();
根据页面效果,源代码解析如下:
- fn 函数执行后,首先,会按照代码执行流程,先输出“1.开始”。
- 其次,对于没有异步请求的内容,在 await 后面都将会正常输出,因此,再输出“2.正在操作”。
- 如果 await 后面是异步请求,那么,必须等待请求完成并获取结果后,才会向下执行。
- 根据上述分析,由于 方法 p 是一个异步请求,因此,必须等待它执行完成后,并将返回值赋给变量 p1,再执行向下代码。
- 所以,最后的执行顺序是,先输出 “3.异步请求”,再输出 “4.结束”,在 async 函数中的执行顺序,如下图所示。
async function main(){
let p = new Promise((resolve, reject) => {
// resolve('OK');
reject('Error');
})
//1. 右侧为promise的情况
// let res = await p;
//2. 右侧为其他类型的数据, 但这种情况不常见
// let res2 = await 20;
//3. 如果promise是失败的状态,需要捕获错误来查看错误理由
try{
let res3 = await p;
console.log(res3);
}catch(e){
console.log(e);
}
}
main();
async与await结合实践
1.txt
观书有感
-朱熹
半亩方塘一鉴开
天光云影共徘徊。
2.txt
问渠那得清如许?
为有源头活水来。
3.txt
---------
中华古诗词
const fs = require('fs')
const util= require('util')
// 将fs.readFile promise化,即将异步函数转换成同步的形式(只是转换形式,实质还是异步)
const myReadFile=util.promisify(fs.readFile)
// 传统的读多个文件,并将文件内容串联起来输出。缺点是会有多个回调嵌套
// fs.readFile('./resource/1.txt',(err,data1)=>{
// if(err) throw err;
// fs.readFile('./resource/2.txt',(err,data2)=>{
// if(err) throw err;
// fs.readFile('./resource/2.txt',(err,data3)=>{
// if(err) throw err;
// console.log(data1+data2+data3);
// })
// })
// })
// 结合使用async和await, 将异步任务以同步的形式调用,减少嵌套,结构更清晰
async function main(){
try{
const data1=await myReadFile('./resource/1.txt')
const data2=await myReadFile('./resource/2.txt')
const data3=await myReadFile('./resource/3.txt')
console.log(data1+data2+data3);
}catch(e){
console.log(e);
}
}
main()
async与await结合发送AJAX请求
//axios是基于Promise封装的AJAX请求库
function sendAJAX(url){
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open("GET", url);
xhr.send();
//处理结果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
//判断成功
if(xhr.status >= 200 && xhr.status < 300){
//成功的结果
resolve(xhr.response);
}else{
reject(xhr.status);
}
}
}
});
}
//段子接口地址 https://api.apiopen.top/getJoke
let btn = document.querySelector('#btn');
btn.addEventListener('click',async function(){
//获取段子信息
let duanzi = await sendAJAX('https://api.apiopen.top/getJoke');
console.log(duanzi);
});
经典题——执行顺序
例题
async function async1() {
console.log('async1 start')
await async2();
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1();
new Promise((resolve) => {
console.log('promise1')
resolve();
}).then(() => {
console.log('promise2')
})
console.log('script end')
执行结果如下
解析:
执行顺序是同步任务–>微任务–>宏任务,只有上游的任务全部执行完才能执行下游的任务。Promise中的代码是同步的,then/catch/finally中的代码是异步的微任务。async标记的函数会原地等待await表达式的结果,await表达式后面的代码就相当于then里面的代码,属于异步的微任务。
---任务栈---
script start
async1 start
async2
promise1
script end
---微任务队列---
async1 end
promise2
---宏任务队列---
setTimeout
变式一
async function async1() {
console.log('async1 start')
await async2();
await async3();
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
async function async3() {
console.log('async3')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1();
new Promise((resolve) => {
console.log('promise1')
resolve();
}).then(() => {
console.log('promise2')
})
console.log('script end')
结果:
解析:注意这题有微任务的嵌套,同一个async标记的函数中若有多个await,那么就存在微任务的嵌套(微任务套着微任务)。
先将任务按照顺序分好类
首先执行同步任务,所以依次输出
此时状态如下
再执行如下微任务
先执行await async3()
,控制台输出async3,注意await async3()
后面的内容又是一个微任务,所以console.log('async1 end')
添加进微任务队列
此时状态如下
然后按照微任务–>宏任务的顺序执行,所以再依次输出:
变式三
async function async1() {
console.log('async1 start')
await async2();
await async3();
console.log('async1 end')
setTimeout(function () {
console.log('async4')
}, 0)
}
async function async2() {
console.log('async2')
}
async function async3() {
setTimeout(function () {
console.log('async3')
}, 0)
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1();
new Promise((resolve) => {
console.log('promise1')
resolve();
}).then(() => {
console.log('promise2')
})
console.log('script end')
结果
解析:按照变式二解析的思路去推导即可得出
变式四
setTimeout(()=>{
console.log(0);
},0)
new Promise((resolve,reject)=>{
console.log(1);
resolve();
}).then(()=>{
console.log(2);
new Promise((resolve,reject)=>{
console.log(3);
resolve()
}).then(()=>{
console.log(4);
}).then(()=>{
console.log(5);
})
}).then(()=>{
console.log(6);
})
new Promise((resolve, reject) => {
console.log(7);
resolve();
}).then(()=>{
console.log(8);
})
结果:
解析:
先将任务分好类
先执行同步任务,输出1,7
然后执行第一块微任务代码,依次输出2,3后,遇到then中的微任务代码,将打印4,5,6的微任务代码块添加进微任务队列。注意只有上一个then中的内容都执行完了才能执行下一个then的内容
此时状态如下
然后按照微任务–>宏任务的顺序执行,再依次输出8,4,6,5,0(?8,4,5,6,0)
故最终结果为