其实想写 Promise 的使用已经很长时间了。一个是在实际编码的过程中经常用到,一个是确实有时候小伙伴们在使用时也会遇到一些问题。
Promise 也确实是 ES6 中 对于写 JS 的方式,有着真正最大影响的 API 特性之一。
本文是实际使用使用过程中的一个总结
看一下文件创建时间 2017-10-09,拖延症真是太可怕了。。。还是得增强执行力啊!不忘初心,加油吧!
博客原址

前言 && 基础概念

Promise 是解决 JS 异步的一种方案,相比传统的回调函数,Promise 能解决多个回调严重嵌套的问题。

Promise 对象代表一个异步操作,有三种状态: pending、fulfilled 或 rejected ,状态的转变只能是 pending -> fulfilled 或者 pending -> rejected ,且这个过程一旦发生就不可逆转

<!-- more -->

个人认为讲解 Promise 实际上需要分成两个部分

  1. 对于 Promise 构造函数的使用说明。
  2. Promise 原型对象上的一些方法。

Promise 构造函数

ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。

Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject 。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve 函数的作用是将 Promise 对象的状态从“未完成”变为“成功”(即从 pending 变为 fulfilled ),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject 函数的作用是,将 Promise 对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected ),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

下面代码创造了一个 Promise 实例。


function request() {
return new Promise((resolve, reject) =&gt; {
/* 异步操作成功 */
setTimeout(() =&gt; {
resolve("success");
}, 1000);
// 取消注释这里可以体现,Promise 的状态一旦变更就不会再变化的特性
// reject('error');
});
}

接收


request()
.then(result =&gt; {
console.info(result);
})
.catch(error =&gt; {
console.info(error);
});

上述 new Promise() 之后,除去用 catch 去捕获错误之外,也可以用 then 方法指定 resolvereject 的回调函数
也能达到捕获错误的目的。


request().then(
result =&gt; {
console.info(result);
},
error =&gt; {
console.info(error);
}
);

原型上的方法

Promise.prototype.then()

p.then(onFulfilled, onRejected);

then 方法 是定义在 Promise.prototype 上的方法,如上面的例子一样,有两个参数,fulfilled 的回调函数和 rejected 的回调函数,第二个参数时可选的。

两个关键点:

  1. then 方法的返回值是一个新的 Promise 实例,所以对于调用者而言,拿到一个 Promise 对象,调用 then 后仍然返回一个 Promise ,而它的行为与 then 中的回调函数的返回值有关。如下:
  • 如果 then 中的回调函数返回一个值,那么 then 返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
  • 如果 then 中的回调函数抛出一个错误,那么 then 返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
  • 如果 then 中的回调函数返回一个已经是接受状态的 Promise,那么 then 返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的 Promise 的接受状态回调函数的参数值。
  • 如果 then 中的回调函数返回一个已经是拒绝状态的 Promise,那么 then 返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的 Promise 的拒绝状态回调函数的参数值。
  • 如果 then 中的回调函数返回一个未定状态(pending)的 Promise,那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。
  1. 链式调用。把嵌套回调的代码格式转换成一种链式调用的纵向模式。

比如说回调形式: 一个回调地狱的例子


a(a1 =&gt; {
b(a1, b1 =&gt; {
c(b1, c1 =&gt; {
d(c1, d1 =&gt; {
console.log(d1);
});
});
});
});

这样的横向扩展可以修改成(a,b,c,d)均为返回 Promise 的函数


a()
.then(b)
.then(c)
.then(d)
.then(d1 =&gt; {
console.log(d1);
});
//===== 可能上面的例子并不太好看 ===下面这样更直观
a()
.then(a1 =&gt; b(a1))
.then(b1 =&gt; c(b1))
.then(c1 =&gt; d(c1))
.then(d1 =&gt; {
console.log(d1);
});

这样的纵向结构,看上去清爽多了。

Promise.prototype.catch()

除了 then() ,在 Promise.prototype 原型链上的还有 catch() 方法,这个是拒绝的情况的处理函数。

其实 它的行为与调用 Promise.prototype.then(undefined, onRejected) 相同。 (事实上, calling obj.catch(onRejected) 内部 calls obj.then(undefined, onRejected)).


// 1.
request().then(
result =&gt; {
console.info(result);
},
error =&gt; {
console.info(error);
}
); // 2.
request()
.then(result =&gt; {
console.info(result);
})
.catch(error =&gt; {
console.info(error);
});

如上这个例子:两种方式在使用,与结果基本上是等价的,但是 仍然推荐第二种写法,下面我会给出原因:

  1. 在 Promise 链中 Promise.prototype.then(undefined, onRejected)onRejected 方法无法捕获当前 Promise 抛出的错误,而后续的 .catch 可以捕获之前的错误。
  2. 代码冗余

new Promise((resolve, reject) =&gt; {
setTimeout(() =&gt; {
resolve("reject");
}, 1000);
})
.then(
result =&gt; {
console.log(result + "1");
throw Error(result + "1"); // 抛出一个错误
},
error =&gt; {
console.log(error + ":1"); // 不会走到这里
}
)
.then(
result =&gt; {
console.log(result + "2");
return Promise.resolve(result + "2");
},
error =&gt; {
console.log(error + ":2");
}
);
// reject1, Error: reject1:2

如果使用 .catch 方法,代码会简化很多,这样实际上是延长了 Promise 链


new Promise((resolve, reject) =&gt; {
setTimeout(() =&gt; {
resolve("reject");
}, 1000);
})
.then(result =&gt; {
console.log(result + "1");
throw Error(result + "1"); // 抛出一个错误
})
.then(result =&gt; {
console.log(result + "2");
return Promise.resolve(result + "2");
})
.catch(err =&gt; {
console.log(err);
});
// reject1, Error: reject1:2

Promise.prototype.finally()

暂未完全成为标准的一部分,处于:Stage 4

finally() 方法返回一个 Promise,在执行 then()catch() 后,都会执行finally指定的回调函数。(回调函数中无参数,仅仅代表 Promise 的已经结束

等同于使用 .then + .catch 延长了原有的 Promise 链的效果,避免同样的语句需要在 then()catch() 中各写一次的情况。

mdn-Promise-finally

Promise 对象上的方法

Promise.all() 用来处理 Promise 的并发

Promise.all 会将多个 Promise 实例封装成一个新的 Promise 实例,新的 promise 的状态取决于多个 Promise 实例的状态,只有在全体 Promise 都为 fulfilled 的情况下,新的实例才会变成 fulfilled 状态。;如果参数中 Promise 有一个失败(rejected),此实例回调失败(rejecte),失败原因的是第一个失败 Promise 的结果。

举个例子:


Promise.all([
new Promise(resolve =&gt; {
setTimeout(resolve, 1000, "p1");
}),
new Promise(resolve =&gt; {
setTimeout(resolve, 2000, "p2");
}),
new Promise(resolve =&gt; {
setTimeout(resolve, 3000, "p3");
})
])
.then(result =&gt; {
console.info("then", result);
})
.catch(error =&gt; {
console.info("catch", error);
});
// [p1,p2,p3] Promise.all([
new Promise(resolve =&gt; {
setTimeout(resolve, 1000, "p1");
}),
new Promise(resolve =&gt; {
setTimeout(resolve, 2000, "p2");
}),
Promise.reject("p3 error")
])
.then(result =&gt; {
console.info("then", result);
})
.catch(error =&gt; {
console.info("catch", error);
});
// p3 error

获取 cnode 社区的 精华贴的前十条内容


fetch("https://cnodejs.org/api/v1/topics?tab=good&amp;limit=10")
.then(res =&gt; res.json())
.then(res =&gt; {
const fetchList = res.data.map(item =&gt; {
return fetch(`https://cnodejs.org/api/v1/topic/${item.id}`)
.then(res =&gt; res.json())
.then(res =&gt; res.data);
});
Promise.all(fetchList).then(list =&gt; {
console.log(list);
});
});

Promise.race() 竞态执行

Promise.race 也会将多个 Promise 实例封装成一个新的Promise实例,只不过新的 Promise 的状态取决于最先改变状态的 Promise 实例的状态。

在前端最典型的一个用法是为 fetch api 模拟请求超时。


Promise.race([
fetch("https://cnodejs.org/api/v1/topics?tab=good&amp;limit=10").then(res =&gt;
res.json()
),
new Promise((resolve, reject) =&gt; {
setTimeout(reject, 1, "error");
})
])
.then(result =&gt; {
console.info("then", result);
})
.catch(error =&gt; {
console.info("catch", error); // 进入这里
});

上述例子中只要请求 未在 1 毫秒内结束就会进入 .catch() 方法中,虽然不能将请求取消,但是超时模拟却成功了

Promise.resolve(value) && Promise.reject(reason)

这两个方法都能用来创建并返回一个新的 Promise , 区别是 Promise.resolve(value) 携带进新的 Promise 状态是 fulfilled。而 Promise.reject(reason) 带来的 rejected

有的时候可以用来简化一些创建 Promise 的操作如:


const sleep = (time = 0) =&gt; new Promise(resolve =&gt; setTimeout(resolve, time));
// 这里创建一个 睡眠,并且打印的链
Promise.resolve()
.then(() =&gt; {
console.log(1);
})
.then(() =&gt; sleep(1000))
.then(() =&gt; {
console.log(2);
})
.then(() =&gt; sleep(2000))
.then(() =&gt; {
console.log(3);
});

有时也用来 手动改变 Promise 链中的返回状态 ,当然这样实际上和 直接返回一个值,或者是 使用 throw Error 来构造一个错误,并无区别。到底要怎么用 就看个人喜好了


new Promise((resolve, reject) =&gt; {
setTimeout(() =&gt; {
resolve("resolve"); // 1.
}, 1000);
})
.then(result =&gt; {
return Promise.reject("reject1"); // 2.
})
.then(
result =&gt; {
return Promise.resolve(result + "2");
},
err =&gt; {
return Promise.resolve(err); // 3.
}
)
.then(res =&gt; {
console.log(res); // 4.
})
.catch(err =&gt; {
console.log(err + "err");
});
// reject1

几个例子

下面来看几个例子:

关于执行顺序,具体可搜索,js 循环


new Promise((resolve, reject) =&gt; {
console.log("step 1");
resolve();
console.log("step 2");
}).then(() =&gt; {
console.log("step 3");
});
console.log("step 4"); // step 1, step 2, step 4 , step 3

在使用 Promise 构造函数构造 一个 Promise 时,回调函数中的内容就会立即执行,而 Promise.then 中的函数是异步执行的。

关于状态不可变更


let start;
const p = new Promise((resolve, reject) =&gt; {
setTimeout(() =&gt; {
start = Date.now();
console.log("once");
resolve("success");
}, 1000);
});
p.then(res =&gt; {
console.log(res, Date.now() - start);
});
p.then(res =&gt; {
console.log(res, Date.now() - start);
});
p.then(res =&gt; {
console.log(res, Date.now() - start);
});

Promise 构造函数只执行一次,内部状态一旦改变,有了一个值,后续不论调用多少次then()都只拿到那么一个结果。

关于好像状态可以变更


const p1 = new Promise((resolve, reject) =&gt; {
setTimeout(() =&gt; {
resolve("success");
}, 1000);
}); const p2 = p1.then((resolve, reject) =&gt; {
throw new Error("error");
}); console.log("p1", p1);
console.log("p2", p2); setTimeout(() =&gt; {
console.log("p1", p1);
console.log("p2", p2);
}, 2000);

观察这一次的打印
第一次打印出两个 Promise 的时候都是 pending ,因为 p2 是基于 p1 的结果,p1 正在 pending ,立即打印出的时候肯定是 pending ;第二次打印的时候,因为 p1 的状态为 resolved ,p2 为 rejected ,这个并不是已经为 fulfilled 状态改变为 rejected ,而是 p2 是一个新的 Promise 实例,then() 返回新的 Promise 实例。

关于透传


Promise.resolve(11)
.then(1)
.then(2)
.then(3)
.then(res =&gt; {
console.info("res", res);
});
// 11

给 then 方法传递了一个非函数的值,等同于 then(null),会导致穿透的效果,就是直接过掉了这个 then() ,直到符合规范的 then() 为止。

Promise 的串行调用

使用 Array.reduce 方法串行执行 Promise


const sleep = (time = 0) =&gt; new Promise(resolve =&gt; setTimeout(resolve, time));
[1000, 2000, 3000, 4000].reduce((Promise, item, index) =&gt; {
return Promise.then(res =&gt; {
console.log(index + 1);
return sleep(item);
});
}, Promise.resolve());
// 在分别的等待时间后输出 1,2,3,4

这篇文章到这里就基本上结束了,相信 如果能理解上面的内容,并且在实际项目中使用的话。应该会让工作更高效吧,对于新的异步使用应该也会更加的得心应手。Promise 的使用相对简单,可能后续再出一篇如何实现一个 Promise 吧

那些收集的 Promise 的优质文章。

来源:https://segmentfault.com/a/1190000016713032

学习 Promise,掌握未来世界 JS 异步编程基础的更多相关文章

  1. JS异步编程 (2) - Promise、Generator、async/await

    JS异步编程 (2) - Promise.Generator.async/await 上篇文章我们讲了下JS异步编程的相关知识,比如什么是异步,为什么要使用异步编程以及在浏览器中JS如何实现异步的.最 ...

  2. 一个例子读懂 JS 异步编程: Callback / Promise / Generator / Async

    JS异步编程实践理解 回顾JS异步编程方法的发展,主要有以下几种方式: Callback Promise Generator Async 需求 显示购物车商品列表的页面,用户可以勾选想要删除商品(单选 ...

  3. node.js异步编程解决方案之Promise用法

    node.js异步编程解决方案之Promise var dbBase = require('../db/db_base'); var school_info_db = require('../db/s ...

  4. JS异步编程 (1)

    JS异步编程 (1) 1.1 什么叫异步 异步(async)是相对于同步(sync)而言的,很好理解. 同步就是一件事一件事的执行.只有前一个任务执行完毕,才能执行后一个任务.而异步比如: setTi ...

  5. JS魔法堂:深究JS异步编程模型

    前言  上周5在公司作了关于JS异步编程模型的技术分享,可能是内容太干的缘故吧,最后从大家的表情看出"这条粉肠到底在说啥?"的结果:(下面是PPT的讲义,具体的PPT和示例代码在h ...

  6. js异步编程

    前言 以一个煮饭的例子开始,例如有三件事,A是买菜.B是买肉.C是洗米,最终的结果是为了煮一餐饭.为了最后一餐饭,可以三件事一起做,也可以轮流做,也可能C需要最后做(等A.B做完),这三件事是相关的, ...

  7. 深究JS异步编程模型

    前言  上周5在公司作了关于JS异步编程模型的技术分享,可能是内容太干的缘故吧,最后从大家的表情看出"这条粉肠到底在说啥?"的结果:(下面是PPT的讲义,具体的PPT和示例代码在h ...

  8. node.js异步编程的几种模式

    Node.js异步编程的几种模式 以读取文件为例: 1.callback function const fs = require('fs'); //callback function fs.readF ...

  9. 前端分享----JS异步编程+ES6箭头函数

    前端分享----JS异步编程+ES6箭头函数 ##概述Javascript语言的执行环境是"单线程"(single thread).所谓"单线程",就是指一次只 ...

随机推荐

  1. ylb: 触发器(Trigger)之Instead Of触发器 [注:没内容]

    ylbtech-SQL Server:SQL Server-触发器(Trigger)之Instead Of触发器 触发器(Trigger)之Instead Of触发器 [注:没内容]. ylb: 触发 ...

  2. StringUtils和IOUtils工具包的使用

    加载apache的工具类 <dependency> <groupId>commons-lang</groupId> <artifactId>common ...

  3. 线程安全的概念和Synchronized(读书笔记)

         并行程序开发的一大关注重点就是线程安全,一般来说,程序并行化为了获取更多的执行效率,但前提是,高效率不能以牺牲正确性为代价,线程安全就是并行程序的根本和根基.volatile并不能真正保证线 ...

  4. 常用组件介绍 ---- Layout_weight

      下面这些也可以算是组件 文本区 TextView 文本框 EditText layout  容器 view     千万不要把Layout_weight 与 Layout_width相混淆**** ...

  5. C#获取webbrowser完整cookie

    [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)] //API设定Cookie stat ...

  6. linux 源代码安装mysql5.5

    linux下源代码安装mysql过程例如以下: yum update yum upgrade yum install -y vim man wget yum install -y gcc gcc-c+ ...

  7. type-c UCSI和UcmCx.sys文件

    简介:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/usbcon/ucsi UCSI:USB Type-C Connector S ...

  8. HBase——完全分布

    实际上,在真实环境中你需要使用完全分布配置完整测试HBase.在一个分布式配置中,集群有多个节点,每个节点运行一个或多个HBase守护进程.其中包括主Master和备份Master实例,多个Zooke ...

  9. NativeBase自定义组件样式

    http://nativebase.io/docs/v0.5.13/customize#themingNativeBaseApp 对于NativeBase中的组件,我们可以根据实际需要来进行自定义组件 ...

  10. 不同特权级间代码段的跳转{ 门 + 跳转(jmp + call) + 返回(ret) }

    [0]写在前面 0.1)我们讲 CPU的保护机制,它是可靠的多任务运行环境所必须的: 0.2) CPU保护机制:分为段级保护 + 页级保护: 0.2.1)段级保护分为:段限长 limit 检查.段类型 ...