本套笔记,是基于尚硅谷的课程整理

​Javascript(笔记51) - promise - 1 介绍与基本使用 ​

​Javascript(笔记52) - promise - 2 API和方法​

​Javascript(笔记53) - promise - 3 几个关键问题​

​Javascript(笔记54) - promise - 4 async函数、await表达式、结合示例​

补充:

​Javascript(笔记38) - ES6特性 - Promise​

​Javascript(笔记45) - ES8特性 - async 和 await​


promise 是ES6引入的异步编程新的解决方案;从语法上来说,promise 就是个构造函数,可以封装异步的任务,并对结果进行处理;promise 最大的好处,在于可以解决回调地狱的问题,并且在指定回调和错误处理方面更灵活;目前前端、后端用的都比较多,面试也经常问到;

基础要求:需要对 javascript, ajax 和 node 有一定的了解;


Promise是什么

理解

抽象表达:Promise是一门新的技术(ES6规范),是 JS 中进行异步编程的新解决方案

具体表达:语法上说,Promise 是个构造函数,可以进行对象的实例化;功能上说,Promise 对象用来封装异步操作并可获取其“成功/失败”的结果值;

异步编程:fs, 数据库,AJAX,定时器等;

相对于异步操作的新解决方案来说,以前用的都是回调函数的方式来解决;如下:

fs 文件操作:是 node.js 的模块,可对磁盘读写操作

require("fs").readFile('./index.html',(err,data)=>{});

AJAX请求:

$.get('/server',(data)=>{});

定时器:

setTimeout(()=>{},2000);




优点1:回调灵活

1)旧的方式:必须在启动异步任务前指定;

2)Promise:启动异步任务 => 返回 Promise 对象 => 给 Promise 对象绑定回调函数来处理结果

优点2:链式调用

链式调用,可以解决回调地狱的问题;

回调地狱:就是一个异步任务中套着另一个异步任务,形如:

asyncFun1(opt,(...args1) =>{
asyncFun2(opt,(...args2) =>{
asyncFun3(opt,(...args3) =>{
// some operation
});
});
});

回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件;

回调地狱的缺点:不便于阅读,不便于异常处理;

回调地狱解决方案:可以用 Promise 的链式调用;


Promise 初体验

需求:点按钮后,等2秒,弹出结果是否中奖,中奖概率是 30%;

传统方法来写:

<button id="btn">点击抽奖</button>
<script>
// 生成随机数
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1)) + m - 1;
}
// 需要:点南按钮,2S后显示中否中奖(30%概率中奖)
// 若中奖,弹出:恭喜你,高中大奖
// 若未中奖,弹出:再接再厉

// 获取元素对象
const btn = document.querySelector('#btn');
// 绑定按钮
btn.addEventListener('click',function(){
// 30%中奖率 可以取 1-100 的值,如果出现30的数,即中奖;
setTimeout(()=>{
let n = rand(1,100);
console.log(n);
if(n <= 30){
console.log("恭喜你,高中大奖");
}else{
console.log("再接再厉");
}
},2000);
});
</script>

接下来用 Promise来封装:

<button id="btn">点击抽奖</button>
<script>
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1)) + m - 1;
}
const btn = document.querySelector('#btn');
btn.addEventListener('click', function () {
const p = new Promise((fulfill, reject) => {
setTimeout(() => {
let n = rand(1, 100);
if (n <= 30) {
fulfill(); // 将返回的 Promise 对象状态设置为成功
} else {
reject(); // 将对象状态设置为失败
}
}, 2000);
});
p.then(value => {
console.log("恭喜你,高中大奖!"); // 成功时执行
}, reason => {
console.log("再接再厉"); // 失败时执行
});
});
</script>

then() 方法的两个参数也是函数,函数的返回值也是 Promise 对象,可以继续 then 下去;

状态为成功时执行第一个参数的函数,状态为失败时执行第二个参数的函数;

Javascript(笔记51) - promise - 1 介绍与基本使用_promise

现在在增加点需求,把生成的数字显示在结果中:只需要在表示状态的两个函数中传递这个值;

btn.addEventListener('click', function () {
const p = new Promise((fulfill, reject) => {
setTimeout(() => {
let n = rand(1, 100);
if (n <= 30) {
fulfill(n); // 把 生成的 n 通过参数传递给 then 方法处理;
} else {
reject(n);
}
}, 2000);
});
p.then(value => {
console.log(`恭喜你,高中大奖!数字为:${value}`); // 使用了字符串模板来接收传来的值
}, reason => {
console.log(`再接再厉!${reason}`);
});
});

Javascript(笔记51) - promise - 1 介绍与基本使用_javascript_02

Promise状态的改变

1) pending 变为 resolved(或 fulfilled)  代表成功状态

2) pending 变为 rejected  代表失败状态

说明:状态的改变只有这两种,且一个 Promise 对象只能改变一次;无论变为成功还是失败,都会有一个结果数据,成功的结果数据一般称为 value ,失败的结果数据一般称为 reason。

打印一下,上例中的 p 

console.log(p);

Javascript(笔记51) - promise - 1 介绍与基本使用_promise_03

有个内置属性叫: PromiseState : “rejected” , 这是实例对象中的一个属性;

这个属性的值有3种可能:pending, resolved(或fulfilled),rejected


Promise 对象的值

这是 Promise 实例对象的另一个属性,保存着对象异步任务“成功/失败”的结果: PromiseResult :88 

resolve ,reject 只有这两个函数可以给 PrmiseResult 属性赋值,其他人是不能修改的;

在后续的 then() 方法中,可以把这个值取出来,进一步操作;


Promise 的工作流程

Javascript(笔记51) - promise - 1 介绍与基本使用_promise_04

待定(pending):初始状态,既没有被兑现,也没有被拒绝。

已兑现(fulfilled):意味着操作成功完成。  

已拒绝(rejected):意味着操作失败

fulfill 返回的状态对应着 then() 方法的第一个函数参数;

reject 返回的状态对应着 then() 方法的第二个函数参数;


练习:封装fs读取文件

回调方式来写:需要node环境;

新建 promise_fs.js

const fs = require('fs');
fs.readFile('./res/content.md',(err,data)=>{
if(err) console.log('出错了');
console.log(data.toString()); // 把 buffer 的内容转成字符串
});

注意:这个只能在终端通过命令行来执行:

> node promise_fs.js

Javascript(笔记51) - promise - 1 介绍与基本使用_promise_05

成功读取到文件内容;

使用Promise方式封装

const fs = require('fs');
const p = new Promise((fulfill,reject)=>{
fs.readFile('./res/content.md',(err,data)=>{
if(err) reject(err); // 出错时调用,状态为失败
fulfill(data); // 成功时调用,状态为成功
});
});
p.then(value=>{ // 调用 then
console.log(value.toString()); // 输出正确数据,buffer 转字符串
},reason=>{
console.log(reason); // 输出错误对象
});

正确的输出跟上面一样;测试一下出错的情况:随意修改下路径为 content.md22;

Javascript(笔记51) - promise - 1 介绍与基本使用_javascript_06

练习:封装AJAX

AJAX笔记: ​​Javascript(笔记49) - AJAX - AJAX简介、工作流程、同步、异步​


需求:点击按钮,使用AJAX从接口拿到数据

免费开放接口:​https://api.apiopen.top/api/sentences

传统写法:注释部分就省去了;

<button id="btn">点击发送 AJAX</button>
<script>
let btn = document.querySelector('#btn');
btn.addEventListener('click',function(){
let xhr = new XMLHttpRequest();
xhr.open('GET','https://api.apiopen.top/api/sentences');
xhr.send(null);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status >= 200 && xhr.status < 300){
console.log(xhr.response);
}else{
console.log(xhr.status);
}
}
}
});
</script>

Javascript(笔记51) - promise - 1 介绍与基本使用_javascript_07


Promise封装:

<button id="btn">点击发送 AJAX</button>
<script>
let btn = document.querySelector('#btn');
btn.addEventListener('click',function(){
const p = new Promise((fulfill,reject)=>{
const xhr = new XMLHttpRequest();
xhr.open('GET','https://api.apiopen.top/api/sentence');
xhr.send(null);
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status>=200 && xhr.status < 300){
// 状态成功,输出响应体
fulfill(xhr.response);
}else{
// 状态失败,输出状态码
reject(xhr.status);
}
}
}
});
p.then(value=>{
console.log(value);
},reason=>{
console.log(reason);
});
});
</script>

点击按钮,输出结果跟上面一样;


练习:封装函数读取文件

需求:封装一个函数 mineReadFile 读取文件内容;

参数:path 文件路径;

返回:Promise 对象;

在Node环境下封装,新建文件  mineReadFile.js:

/**
* 读取文件内容,返回 Promise 对象
* @param {path} path 为文件路径
*/

function mineReadFile(path){
return new Promise((fulfill,reject)=>{
require('fs').readFile(path,(err,data)=>{
if(err) reject(err);
fulfill(data);
});
});
}
mineReadFile('./res/content.md') // 调用封装的函数
.then(value=>{
console.log(value.toString());
},reason=>{
console.log(reason);
});

Javascript(笔记51) - promise - 1 介绍与基本使用_javascript_08

使用封装好的读取文件函数,返回的是 Promise 对象,之后可以跟 then() 方法来输出数据;


练习:util.promisify

传一个遵循常见的错误优先的回调风格的函数(即以(err,value)=>...回调作为最后一个参数),并返回一个返回 promise 的版本。

原文:Takes a function following the common error-first callback style, i.e. taking an (err, value) => ... callback as the last argument, and returns a version that returns promises.

说白了就是错误优先的回调,如 fs 等,异步的 Api,都是错误优先回调。

// util.promisify 方法
// 引入 util 模块
const util = require('util');
// 引入 fs 模块
const fs = require('fs');
// 返回一个新的函数,这个函数在调用之后,它的返回结果也是 Promise 对象
let mineReadFile = util.promisify(fs.readFile);
mineReadFile('./res/content.md').then(value=>{
console.log(value.toString());
});

Javascript(笔记51) - promise - 1 介绍与基本使用_promise_09

总结: util.promisify 这个方法,可以将原来形如 (err,data)=>{回调函数} 风格的方法转换成 Promise c 风格的函数,使用起来会比较方便;

fs.readFile('path',(err,data)=>{});

由 util.promisify 转换成 Promise 风格的函数:

let mineReadFile = util.promisify(fs.readFile);

fs.readFile 看起来是完成符合这种特征,因此可以使用 util.promisify 方法来转换成 Pormise 风格的函数来调用,后跟 then() 方法来输出结果;


练习:封装AJAX的GET请求

/**
* 封装一个函数 sendAjax 发送 GET 请求
* 参数: URL
* 返回结果: Promise 对象
*/
function sendAjax(url) {
return new Promise((fulfill, reject) => {
const xhr = new XMLHttpRequest();
// xhr.responseType = 'json';
xhr.open("GET", url);
xhr.send(null);
// 处理结果,绑定事件
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 成功状态,返回响应体
fulfill(xhr.response);
}else{
// 失败状态,返回状态码
reject(xhr.status);
}
}
}
});
}
//地址为开放接口
sendAjax('https://api.apiopen.top/api/getImages?page=0&size=10')
.then(value=>{
console.log(value.toString());
},reason=>{
console.log(reason);
});