JS 异步系列 —— Promise 札记
Promise
研究 Promise 的动机大体有以下几点:
对其 api 的不熟悉以及对实现机制的好奇;
很多库(比如 fetch)是基于 Promise 封装的,那么要了解这些库的前置条件得先熟悉 Promise;
要了解其它更为高级的异步操作得先熟悉 Promise;
基于这些目的,实践了一个符合 Promise/A+ 规范的 repromise。
本札记系列总共三篇文章,作为之前的文章 Node.js 异步异闻录 的拆分和矫正。
Promise/A+ 核心

在实现一个符合 Promise/A+ 规范的 promise 之前,先了解下 Promise/A+ 核心,想更全面地了解可以阅读 Promise/A+规范
- Promise 操作只会处在 3 种状态的一种:未完成态(pending)、完成态(resolved) 和失败态(rejected);
- Promise 的状态只会出现从未完成态向完成态或失败态转化;
- Promise 的状态一旦转化,将不能被更改;
repromise api 食用手册
Promise.resolve()
Promise.resolve() 括号内有 4 种情况
/* 跟 Promise 对象 */
Promise.resolve(Promise.resolve(1))
// Promise {state: "resolved", data: 1, callbackQueue: Array(0)}
/* 跟 thenable 对象 */
var thenable = {
then: function(resolve, reject) {
resolve(1)
}
}
Promise.resolve(thenable)
// Promise {state: "resolved", data: 1, callbackQueue: Array(0)}
/* 普通参数 */
Promise.resolve(1)
// Promise {state: "resolved", data: 1, callbackQueue: Array(0)}
/* 不跟参数 */
Promise.resolve()
// Promise {state: "resolved", data: undefined, callbackQueue: Array(0)}
Promise.reject()
相较于 Promise.resolve(),Promise.reject() 原封不动地返回参数值
Promise.all(arr)
对于 Promise.all(arr) 来说,在参数数组中所有元素都变为决定态后,然后才返回新的 promise。
// 以下 demo,请求两个 url,当两个异步请求返还结果后,再请求第三个 url
const p1 = request(`http://some.url.1`)
const p2 = request(`http://some.url.2`)
Promise.all([p1, p2])
.then((datas) => { // 此处 datas 为调用 p1, p2 后的结果的数组
return request(`http://some.url.3?a=${datas[0]}&b=${datas[1]}`)
})
.then((data) => {
console.log(msg)
})
Promise.race(arr)
对于 Promise.race(arr) 来说,只要参数数组有一个元素变为决定态,便返回新的 promise。
// race 译为竞争,同样是请求两个 url,当且仅当一个请求返还结果后,就请求第三个 url
const p1 = request(`http://some.url.1`)
const p2 = request(`http://some.url.2`)
Promise.race([p1, p2])
.then((data) => { // 此处 data 取调用 p1, p2 后优先返回的结果
return request(`http://some.url.3?value=${data}`)
})
.then((data) => {
console.log(data)
})
Promise.wrap(fn) —— 回调函数转 Promise
通过下面这个案例,提供回调函数 Promise 化的思路。
function foo(a, b, cb) {
ajax(
`http://some.url?a=${a}&b=${b}`,
cb
)
}
foo(1, 2, function(err, data) {
if (err) {
console.log(err)
} else {
console.log(data)
}
})
如上是一个传统回调函数使用案例,只要使用 Promise.wrap() 包裹 foo 函数就对其完成了 promise 化,使用如下:
const promiseFoo = Promise.wrap(foo)
promiseFoo(1, 2)
.then((data) => {
console.log(data)
})
.catch((err) => {
console.log(err)
})
Promise.wrap 的实现逻辑也顺带列出来了:
Promise.wrap = function(fn) {
return funtion() {
const args = [].slice.call(arguments)
return new Promise((resolve, reject) => {
fn.apply(null, args.concat((err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
}))
})
}
}
then/catch/done
这几个 api 比较简单,合起来一起带过
Promise.resolve(1)
.then((data) => {console.log(data)}, (err) => {console.log(err)}) // 链式调用,可以传一个参数(推荐),也可以传两个参数
.catch((err) => {console.log(err)}) // 捕获链式调用中抛出的错误 || 捕获变为失败态的值
.done() // 能捕获前面链式调用的错误(包括 catch 中),可以传两个参数也可不传
实践过程总结
坑点 1:事件循环
事件循环:同步队列执行完后,在指定时间后再执行异步队列的内容。
之所以要单列事件循环,因为代码的执行顺序与其息息相关,此处用 setTimeout 来模拟事件循环;
下面代码片段中,① 处执行完并不会马上执行 setTimeout() 中的代码(③),而是此时有多少次 then 的调用,就会重新进入 ② 处多少次后,再进入 ③
excuteAsyncCallback(callback, value) {
const that = this
setTimeout(function() {
const res = callback(value) // ③
that.excuteCallback('fulfilled', res)
}, 4)
}
then(onResolved, onRejected) {
const promise = new this.constructor()
if (this.state !== 'PENDING') {
const callback = this.state === 'fulfilled' ? onResolved : onRejected
this.excuteAsyncCallback.call(promise, callback, this.data) // ①
} else {
this.callbackArr.push(new CallbackItem(promise, onResolved, onRejected)) // ②
}
return promise
}
坑点 2:this 的指向问题
this.callbackArr.push() 中的 this 指向的是 ‘上一个’ promise,所以类 CallbackItem 中,this.promise 存储的是'下一个' promise(then 对象)。
class Promise {
...
then(onResolved, onRejected) {
const promise = new this.constructor()
if (this.state !== 'PENDING') { // 第一次进入 then,状态是 RESOLVED 或者是 REJECTED
const callback = this.state === 'fulfilled' ? onResolved : onRejected
this.excuteAsyncCallback.call(promise, callback, this.data) // 绑定 this 到 promise
} else { // 从第二次开始以后,进入 then,状态是 PENDING
this.callbackArr.push(new CallbackItem(promise, onResolved, onRejected)) // 这里的 this 也是指向‘上一个’ promise
}
return promise
}
...
}
class CallbackItem {
constructor(promise, onResolve, onReject) {
this.promise = promise // 相应地,这里存储的 promise 是来自下一个 then 的
this.onResolve = typeof(onResolve) === 'function' ? onResolve : (resolve) => {}
this.onReject = typeof(onRejected) === 'function' ? onRejected : (rejected) => {}
}
...
}
more
实践的更多过程可以参考测试用例。有好的意见欢迎交流。
JS 异步系列 —— Promise 札记的更多相关文章
- JS 异步与 Promise
JS 异步与 Promise 本文写于 2020 年 6 月 8 日 1. 同步与异步与回调函数 Promise 现在是前端面试必考题呀,但是先不急着看 Promise,我们首先来看看什么是异步. - ...
- 【Mocha.js 101】同步、异步与 Promise
前情提要 在上一篇文章<[Mocha.js 101]Mocha 入门指南>中,我们提到了如何用 Mocha.js 进行前端自动化测试,并做了几个简单的例子来体验 Mocha.js 给我们带 ...
- 深入理解 JavaScript 异步系列(3)—— ES6 中的 Promise
第一部分,Promise 加入 ES6 标准 原文地址 http://www.cnblogs.com/wangfupeng1988/p/6515855.html 未经作者允许不得转载! 从 jquer ...
- JS异步编程 (2) - Promise、Generator、async/await
JS异步编程 (2) - Promise.Generator.async/await 上篇文章我们讲了下JS异步编程的相关知识,比如什么是异步,为什么要使用异步编程以及在浏览器中JS如何实现异步的.最 ...
- node.js异步编程解决方案之Promise用法
node.js异步编程解决方案之Promise var dbBase = require('../db/db_base'); var school_info_db = require('../db/s ...
- 学习 Promise,掌握未来世界 JS 异步编程基础
其实想写 Promise 的使用已经很长时间了.一个是在实际编码的过程中经常用到,一个是确实有时候小伙伴们在使用时也会遇到一些问题.Promise 也确实是 ES6 中 对于写 JS 的方式,有着真正 ...
- 一个例子读懂 JS 异步编程: Callback / Promise / Generator / Async
JS异步编程实践理解 回顾JS异步编程方法的发展,主要有以下几种方式: Callback Promise Generator Async 需求 显示购物车商品列表的页面,用户可以勾选想要删除商品(单选 ...
- generator和promise配合解决js异步地狱问题
为何要使用generator函数和promise? js的异步地狱一直是困扰前端程序员的一个头疼的问题 比如说我要获取还有列表,一般来说会使用ajax来获取 $.ajax(...等等,function ...
- js 深入原理讲解系列-Promise
js 深入原理讲解系列-Promise 能看懂这一题你就掌握了 js Promise 的核心原理 不要专业的术语,说人话,讲明白! Q: 输出下面 console.log 的正确的顺序? const ...
随机推荐
- Maven和Solr简单总结
一.1.Maven介绍 Maven是一个项目管理工具,Maven通过POM项目对象模型,对象项目进行管理,通过一个配置文件(xml文件)进行项目的管理.对象项目的声明周期中每个阶段进行管理(清理,编译 ...
- Java的内存 -JVM 内存管理
一.综述 如果你学过C或者C++,那么你应该感受过它们对内存那种强大的掌控力.但是强大的能力往往需要更强大的控制力才能保证能力不被滥用,如果滥用C/C++的内存管理那么很容易出现指针满天飞的情况,不出 ...
- 关于linux 安装libxml2
安装php的时候提示libxml2 未安装 从服务器安装libxml2 提示 libxml.c:3821: error: expected '=', ',', ';', 'asm' or '__att ...
- Oracle EBS FORM lov
存在一种情况: 一个LOV的值当前有效,因此填入保存.但突然无效后,当查询该界面时就会弹出LOV框使其修改. 解决方案: 1. 非常粗暴,不设置校验,在LOV对应的item强行将校验设置为NO. 2. ...
- SQL Server将一列的多行内容拼接成一行
昨天遇到一个SQL Server的问题:需要写一个储存过程来处理几个表中的数据,最后问题出在我想将一个表的一个列的多行内容拼接成一行 比如表中有两列数据 : ep_classes ep_name A ...
- Vue学习之路6-条件渲染
条件指令 所谓条件指令是指满足某个条件时执行哪部分代码,不满足条件时执行哪部分条件代码.vue条件指令有v-if,v-else-if,v-else三个,v-if条件渲染用来指示元素是否移除或者插入,根 ...
- 日志收集(ElasticSearch)串联查询 MDC
之前写过将应用程序或服务程序产生的日志直接写入搜索引擎的博客 其中基本过程就是 app->redis->logstash->elasticsearch 整个链路过程 本来想将re ...
- FCM算法的matlab程序(初步)
FCM算法的matlab程序 在https://www.cnblogs.com/kailugaji/p/9648430.html文章中已经介绍了FCM算法,现在用matlab程序实现它. 作者:凯鲁嘎 ...
- Python爬虫-02:HTTPS请求与响应,以及抓包工具Fiddler的使用
目录 1. HTTP和HTTPS 1.1. HTTP的请求和响应流程:打开一个网页的过程 1.2. URL 2. 客户端HTTP请求 3. Fiddler抓包工具的使用 3.1. 工作原理 3.2. ...
- spark on yarn 集群部署
概述 hadoop2.7.1 spark 1.5.1 192.168.31.62 resourcemanager, namenode, master 192.168.31.63 nodeman ...