本文主要讲解 Promise 的常用方法以及使用技巧,并实现如何手写一个 Promise。
Promise 的出现
回调地狱问题
在传统的 JS 编程中始终存在一个问题,即当当前回调函数的执行依赖于上一个回调函数的执行结果的时候,会形成回调函数层层嵌套的问题,严重影响代码的可读性与可维护性,这种现象一般称之为回调地狱。
下面为示例代码,回调地狱的一个比较常见的情景为ajax请求,即下一个请求的是否发起依赖于上一个请求的结果。
Promise 的出现
Promise是什么?其解决了什么问题?Promise是异步编程的一种解决方案,比传统的解决方案回调函数更合理、更强大。ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。指定回调函数的方式也变得更加灵活易懂,也解决了异步回调地狱的问题旧方案是单纯使用回调函数,常见的异步操作有:定时器、fs 模块、ajax、数据库操作。从语法上说,Promise是一个构造函数;从功能上说,Promise对象用来封装一个异步操作并可以获取其成功/失败的结果值。Promise设计的核心理念是什么?- 从
Promise的角度来说,是将状态改变与回调函数彻底区分开。 - 从应用
Promise的角度来说,以ajax请求数据为例,则是将数据请求与数据处理区分开。
- 从
Promise 的实际应用
案例 1:利用 promise 来进行读取文件操作
案例 2:利用 promise 进行 ajax 请求
案例 3:利用 promise 进行数据库操作
案例 4:封装一个函数,作用是读取文件
node 中的 promisify
promisify(只能在NodeJS环境中使用)promisify是util模块中的一个方法util是nodeJS的内置模块- 作用: 返回一个新的函数, 函数是
promise风格的.
const util = require("util");const fs = require("fs");//通过 fs.readFile 创建一个新的函数const mineReadFile = util.promisify(fs.readFile);
mineReadFile("./resource/2.html").then( (value) => { console.log(value.toString()); }, (reason) => { console.log(reason); });查看 promisify 的手写实现
看到 promisify 这个函数对其内部实现机制比较感兴趣,那我们就来手写一下。
-
首先
promisify函数得返回一个函数,同时返回的这个函数应该返回一个Promise对象,根据这两点我们把函数的基本结构搭建起来。JS const promisify = () => {return function () {return new Promise((resolve, reject) => {});};}; -
利用返回的这个
Promise对象,可以使用then方法进行回调的处理,即原来函数的回调决定这Promise状态的改变以及执行,所以需要将改变Promise对象的状态的回调函数传入参数中。JS /*** @param {(...args) => void} func* @returns {(...args) => Promise<any}*/const promisify = (func) => {return function (...params) {return new Promise((resolve, reject) => {func.call(this, ...params, (err, data) => {if (err) reject(err);else resolve(data);});});};};
Promise 的实例方法
then
注意点:通过then返回的promise的对象的状态由谁决定。
- 若返回非
promise对象或者什么都不返回则,状态为fullfilled。 - 若函数执行过程中抛出错误,则状态为
rejected。 - 若返回一个
promise对象,则状态与返回的promise对象保持一致。
const p = new Promise((resolve, reject) => { resolve("ok");});
let result = p.then( (value) => { throw "错误"; }, (reason) => { console.log(reason); });
console.log(result);catch
能够穿透捕获Promise中的错误,其实相当于Promise.prototype.then(undefined, onRejected)。
finally
finally 是在 ES9(ES2018)中新增的一个特性:表示无论 Promise 对象变成 fufilled 还是 rejected 状态,最终都会被执行。
finally 方法中的回调函数是不接受参数的,因为无论前面是 fulfilled 状态还是 rejected 状态, 它都是执行。
const p = new Promise((resolve, reject) => { // resolve('ok'); reject("error");});p.then((res) => { console.log(res);}) .catch((err) => { console.log(err); }) .finally(() => { console.log("finally"); });Promise 类静态方法
resolve
- 当参数不是
Promise对象的时候,返回promise状态为fullfilled。 - 当参数是
Promise对象的时候,返回的Promise实例的属性由该Promise实例决定。
let p3 = Promise.resolve( new Promise((resolve, reject) => { resolve("success"); }));console.log(p3);
let p4 = Promise.resolve(Promise.resolve(Promise.resolve("OK")));console.log(p4);reject
始终返回状态为rejected的Promise对象
console.log(Promise.reject(123));console.log(Promise.reject(Promise.resolve("ok")));all
接受一个Promise对象的数组,如果数组中的Promise对象的状态都是fullfilled,则返回的也是一个状态为fullfilled对象,PromiseResult的值为每个成功值的数组。若存在状态为rejected的对象则返回一个状态为rejected的Promise对象,PromiseResult为第一个状态为rejected的Promise对象。
let p1 = new Promise((resolve, reject) => { resolve("ok");});let p2 = Promise.resolve("hello");let p3 = Promise.resolve("oh yeah");let result = Promise.all([p1, p2, p3]);console.log(result);race
和all方法类似都是接受一个Promise对象的数组,但其状态与结果由数组中最先改变的Promise对象决定。
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("ok"); }, 2000);});let p2 = Promise.resolve("success");let p3 = Promise.resolve("oh hou");let result = Promise.race([p1, p2, p3]);console.log(result);allSettled
与all方法类似,当数组内所有promise的状态都确定后执行成功的回调。基本上只有成功的回调。
function ajax(url) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.open("get", url, true); xhr.send(); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.responseText); } else { reject(xhr.responseText); } } }; });}any
与all方法正好相反,只要参数实例有一个变成 fulfilled 状态,包装实例就会变成 fulfiilled 状态;
如果所有参数实例都变成 rejected,包装实例就会变成 rejected 状态,结果为一个AggregateError对象。
let p1 = new Promise((resolve, reject) =>{" "}{setTimeout(() => { resolve("ok");}, 1000)}) let p2 = new Promise((resolve, reject) => {setTimeout(() => { resolve("okk");}, 2000)}) let p3 = new Promise((resolve, reject) => {setTimeout(() => { reject("error");}, 3000)}) Promise.any([p1, p2, p3]).then(res => {console.log(res)}).catch(err=> {console.log("error")})关键问题
中断 Promise 链
终止Promise链条,有且仅有一种方式,即返回一个状态为pending的promise对象。
修改 Promise 的状态
//如何修改 promise 对象状态let p = new Promise((resolve, reject) => { //1. resolve // resolve('success'); //2. reject // reject('error'); //3. 抛出错误 异常 // throw '出问题啦! 你说出这样的话 你没有良心!!'; // 状态的改变只有一次 resolve("ok"); reject("error");});console.log(p);Promise 串联多个任务
执行顺序分析
使用 JS 手写一个 promise
由于注释写的比较清除就不分步写了,注意Promise类的静态方法race等,接受的是一个可迭代对象,同时注意在写的时候所用到的技巧,foreach以及数组的原生方法。
值得注意的地方有:
_resolve的单独处理,处理嵌套的Promise对象。- 静态方法接受可迭代对象的方法大致相同,但注意空数组的时候返回什么。
- 注意原生实现的时候
then方法不能使用箭头函数定义,会出现this指向无法指向实例对象的问题。
原生实现
class 实现
原生的都写出来了只能说class版本有手就行,注意class field写法的方法仍然属于实例属性,class field的主要设计目的就是为了解决在类里面使用this的种种不便,精简了部分代码。
评论
评论加载中…






