JavaScript – Promise
前言
我学 Promise 的时候, 那时还没有 es6. 曾经还自己实现过. 但时隔多年, 现在 es6 的 promise 已经很完善了.
这篇作为一个简单的复习. (毕竟我已经 1 年多没有写 JS 了...)
以前写过相关的文章:
angular2 学习笔记 ( Rxjs, Promise, Async/Await 的区别 )
参考
Promise 解决的问题
什么是异步和回调 callback
JS 是单线程, 无法并发处理事情, 但是它可以异步. 比如发 http request, request 通过网卡发出去后, CPU 就去做别的事情, 而不是傻傻等网卡回复.
当 response 回来以后, 网卡通知 CPU, 这时再把任务接回来. 这个动作就叫 callback. 就是你别等我, 我好了会通知你.
callback 的写法长这样
const callback = () => {
console.log('timeout');
};
setTimeout(callback, 2000);
setTimeout 是一个异步函数, 调用后, 游览器会用另一个线程去计算时间, 主线程继续处理其它代码. 时间到, 主线程会被通知, 然后运行后续 (callback) 的代码.
大概是这个概念. 其它的异步函数包括 Ajax, FileReader 等等 (通常涉及到磁盘 IO, 网路请求都会是异步的. 因为做这些事情的时候不需要 CPU).
回调地狱
callback 的写法一旦嵌套就会变成很丑, unreadable.
比如, 我想写一个
delay 3 秒,
运行 console 'a'
再 delay 2 秒
运行 console 'b'
再 delay 1 秒
运行 console 'c'
写出来长这样:
setTimeout(() => {
console.log('a');
setTimeout(() => {
console.log('b');
setTimeout(() => {
console.log('c');
}, 1000);
}, 2000);
}, 3000);
丑不丑? Promise 就是用来解决丑这个问题的. 它可以把嵌套的回调 "打平" flat
Promise 基本用法
Promise 的核心是封装了异步函数的调用, 和 callback 的写法, 记住这个点.
promise 使用是这样的
const promise = new Promise((resolve, _reject) => {
setTimeout(() => {
resolve('return value 123');
}, 3000);
});
promise.then((returnValue) => {
console.log('returnValue', returnValue); // return value 123
});
分 2 个阶段看待
初始化 Promise
Promise 是一个 class. 实例化它会等到 promise 对象.
实例化时, 需要传入一个函数. 函数里面封装了要执行的异步代码.
比如上面的 setTimeout. 或者是 Ajax, FileReader 等等都行.
resolve 是一个 callback 代理, 当异步完成以后. 我们调用 resolve 告知 Promise 异步完成了. 并且返回异步函数的返回值 (比如 Ajax 后的 response data)
注册 callback
注册 callback 是通过 promise 对象来实现的. 调用 .then 函数把 callback 传进去
callback 会在 resolve 的时候被执行, 并且获得异步函数的返回值.
注: 有没有返回值都是 ok 的.
意义何在?
像这样把异步函数和 callback wrap 起来, 意义何在呢? 如果只是 1 个 callback 那么没有什么太大的意义.
记得, Promise 要解决的是嵌套的 callback (回调地狱)
.then Combo
我们把上面的 setTimeout 用 Promise 封装一下
function delayAsync(delayTime: number): Promise<void> {
const promise = new Promise<void>((resolve, _reject) => {
setTimeout(() => {
resolve();
}, delayTime);
});
return promise;
} delayAsync(3000).then(() => {
console.log('a');
});
相等于
setTimeout(() => {
console.log('a');
}, 3000);
return promise + .then combo
如果要再嵌套一个 delay, 你可能会认为是这样写
delayAsync(3000).then(() => {
console.log('a');
delayAsync(2000).then(() => {
console.log('b');
});
});
虽然这个也可以跑, 但是正确的用法不是这样. 而是这样
delayAsync(3000)
.then(() => {
console.log('a');
return delayAsync(2000);
})
.then(() => {
console.log('b');
});
在第一个 then 里, 我们返回了另一个 promise 对象.
然后再第一个 then 之后 combo 了另一个 then.
这样的写法就成功的把嵌套的回调 "打平" 了.
Promise 内部实现原理
其实没有必要懂底层逻辑, 会用就可以了. 简单了解一下到时可以啦.
Promise 对象的 then 负责注册 callback. 同时它返回另一个 promise. 你可以把它理解为一个 child promise (连续几个 .then 就变成了一个 promise chain)
callback 除了可以返回普通的 value 也可以返回一个 promise 对象.
当返回 promise 对象, Promise 就会等待这个 promise 对象 resolve 才执行 callback.
Promise 内部就是维护着 promise chain 和所以 callback 的执行顺序. 这样就做到了 "打平" 的写法了.
感悟
Promise 在 es6 之前就有了, 在 JS 的语法基础上, 通过封装实现另一种调用方式, 让代码更好写, 更好读.
jQuery 也是有这种 feel. 还有 Fluent Builder 模式 也是这样. 都是很聪明的实现.
Promise 的执行顺序
console.log('1');
const promise = new Promise<void>((resolve) => {
console.log('2');
resolve();
console.log('3');
});
promise.then(() => {
console.log('5');
});
console.log('4');
new Promise 传入的函数会马上被执行 (里面通常会调用异步函数, 但并没有强制, 你也可以直接调用 resolve 返回的)
上面我刻意搞了一个同步执行的情况, resolve 虽然马上被执行了, 但是 callback 并没有马上被执行.
一直等到 console.log(4) 完了以后 callback 才被执行.
也就是说任何 promise 的 callback 都会被押后执行, 即使 resolve 没有被异步调用. 这个是唯一需要特别注意的.
after resolve 依然执行代码 ?
best practice 的话, resolve 之后就不应该执行代码了.
刻意习惯性的在 resolve 前加上 return, 确保后续没有执行代码. (不然挺乱的)
const promise = new Promise<void>((resolve) => {
console.log('2');
return resolve();
});
Reject and Catch
上面提到的例子都是 succeed 的情况. 其实 Promise 还有一个强项, 那就是解决异步函数和回调 catch error 的问题.
异步的 catching error 问题
同步代码很容易 catch error
try {
throw new Error('erorr');
} catch (e) {
console.log('error 123');
}
换成异步的话
try {
setTimeout(() => {
throw new Error('error');
}, 2000);
} catch (e) {
console.log('error 123'); // won't be call
}
这时就无法 catch 到 error 了.
Promise Reject & Catch
const promise = new Promise<void>((resolve, reject) => {
if (Math.random() >= 0.5) {
resolve();
} else {
reject();
}
}); promise
.then(() => {
console.log('ok');
})
.catch(() => {
console.log('error');
});
除了 resolve, 还有一个 reject 函数用来告知 Promise 异步函数搞糟了.
除了 .then, 还有一个 .catch 用来捕获 reject 的返回.
它的逻辑是 resolve 就去找 .then 的回调. reject 就去找 .catch 的回调.
此外, 除了 reject, throw 也是可以被 catch 到,
delayAsync(3000)
.then(() => {
throw new Error('error');
return delayAsync(2000);
})
.then(() => {
console.log('b');
})
.catch(() => {
console.log('catch');
});
在第一个 then 回调中使用了 throw, 最后一个 catch 也是可以捕获到的哦. 只要在 promise chain 中, Promise 就会去找得到.
另外, promise chain 中, resolve 和 reject 是可以换来换去的. 在 catch 回调中也可以继续返回 resolve promise, 然后变回 succeed 进入下一个 .then 回调.
before and after
小心坑
上面提到,在 Promise 里面是可以使用 throw 的。
function doSomethingAsync(): Promise<void> {
return new Promise((resolve, reject) => {
throw new Error('test');
});
}
上面这样是 ok 的,不适用 reject 也可以,但是下面这样就不行
function doSomethingAsync(): Promise<void> {
return new Promise((resolve, reject) => {
setTimeout(() => {
throw new Error('test');
}, 3000);
});
}
因为 throw 被 setTimeout wrap 起来了(注:不只是 setTimeout,只要 wrap 起来作用域跑掉就不可以了)。这种情况下就必须使用 reject 才行。
.finally
.then for resolve
.catch for reject
.finally for resolve or reject.
就是说不管结果是 succeed 还是 failed 都执行 finally 的回调.
这个跟 try catch finally 概念差不多.
Promise.resolve & Promise.reject
Promise.resolve 是一个方法, 它接受一个值. 返回一个 promise.
console.log('1');
Promise.resolve('value').then((value) => {
console.log('3', value); // value
});
console.log('2');
通常会这样写有 2 个目的.
delay 操作
当想 delay 一个执行的时候, 通常会使用 setTimeout, 但是 setTimeout 默认是 4ms. 有没有一种方式就把执行押后到当前 stack 运行完呢?
那就是用 Promise
console.log('1');
setTimeout(() => {
console.log('4');
});
Promise.resolve().then(() => {
console.log('3');
});
console.log('2');
同步 / 异步函数
有些函数依据情况来决定是否是异步的. 但即使同步的情况下它依然得返回 Promise, 因为返回接口必须是一样的呀
比如上面的 delay. 当 0 的时候, 不希望用 setTimeout 的话可以这样写.
function delayAsync(delayTime: number): Promise<void> {
if (delayTime === 0) {
return Promise.resolve(); // 直接 resolve
} else {
const promise = new Promise<void>((resolve, _reject) => {
setTimeout(() => {
resolve();
}, delayTime);
});
return promise;
}
}
它和下面这个写法是等价的
return new Promise(resolve => resolve());
Promise.reject() 和 Promise.resolve() 概念是一样, 我就不说了.
Promise all, race, any, allSettled
Promise.all
Promise.all([delayAsync(1000), delayAsync(2000)]).then(([v1, v2]) => {
console.log('done', [v1, v2]);
});
当所有的 delayAsync 都 resolve 之后触发回调, 并且拿到所有的返回值.
注意: 所以 Promise.all 的成员会依照顺序去执行, 但不会等待前者 resolve. 像上面的例子, delayAsync(2000) 不会等 delayAsync(1000) 1 秒后才执行 (不会等哦).
当遇到 reject
只要其中一个 reject, catch 马上会被执行, Promise 不会等待其它 all 的成员.
Promise.all([
delayAsync(1000),
delayAsync(2000),
Promise.reject('c'),
Promise.reject('d'),
])
.then(([v1, v2]) => {
console.log('done', [v1, v2]);
})
.catch((value) => {
console.log('failed', value); // value only contain 'c' 因为其中一个 reject 后, catch 马上就执行了, Promise 不会等其成员了
});
Promise.race
.race 和 .all 的区别是, all 会等到所有成员 succedd 才返回所有值, 而 race 则是第一个 succeed 后直接返回它的 值. 其它的就不等了.
当遇到 reject 的情况就和 .all 一样. 其中一个 reject 立马执行 catch, 不等其它成员.
Promise.any (es2021)
这个方法很新哦.
它和 race 有点像, 主要的区别是面对 reject 的时候.
any 的意思是只要其中一个 succeed 就 ok. 所以当出现 reject 的时候, 它不会像 .all 或者 .race 那样立马去 catch. 只有当全部成员都 reject 才会进入 catch.
比如说, 3 个成员, 第一个 reject, 无所谓, 继续等第二个, 假设等 2 个 succeed resolve, 那么就直接进入 .then 的回调. 返回成员 2 resolve 的值, 不会等待成员 3.
Promise.allSettled (es2020)
allSettled 顾名思义, 就是等所以的 promise 执行完. 它和 .all 不同的地方是, 它不管成员是 succeed 还是 failed.
只要全部有结果以后就会触发 .then 回调, 然后把状态和返回值传进去
Promise.allSettled([delayAsync(1000).then(() => Promise.resolve('value1')), delayAsync(3000).then(() => Promise.reject())]).then(v => {
console.log('v', v);
}).catch((e) => {
console.log('e', e); // never run, even all failed
});
无论怎样 catch 都不会被执行哦, 所以不要写 catch.
.then 回调接受的参数, 表示了每个成员的结果.
请继续看下一篇
JavaScript – 用 Generator 运行异步函数 & await async
JavaScript – Promise的更多相关文章
- [Javascript] Promise
Promise 代表着一个异步操作,这个异步操作现在尚未完成,但在将来某刻会被完成. Promise 有三种状态 pending : 初始的状态,尚未知道结果 fulfilled : 代表操作成功 r ...
- Javascript Promise 学习笔记
1. 定义:Promise是抽象异步处理对象以及对其进行各种操作的组件,它把异步处理对象和异步处理规则采用统一的接口进行规范化. 2. ES6 Promises 标准中定义的API: ...
- 【译】JavaScript Promise API
原文地址:JavaScript Promise API 在 JavaScript 中,同步的代码更容易书写和 debug,但是有时候出于性能考虑,我们会写一些异步的代码(代替同步代码).思考这样一个场 ...
- JavaScript Promise:去而复返
原文:http://www.html5rocks.com/en/tutorials/es6/promises/ 作者:Jake Archibald 翻译:Amio 女士们先生们,请准备好迎接 Web ...
- javaScript Promise 入门
Promise是JavaScript的异步编程模式,为繁重的异步回调带来了福音. 一直以来,JavaScript处理异步都是以callback的方式,假设需要进行一个异步队列,执行起来如下: anim ...
- JavaScript Promise异步实现章节的下载显示
Links: JavaScript Promise:简介 1.一章一章顺序地下载显示下载显示 使用Array.reduce()和Promise.resolve()将各章的下载及显示作为整体串联起来. ...
- Javascript - Promise学习笔记
最近工作轻松了点,想起了以前总是看到的一个单词promise,于是耐心下来学习了一下. 一:Promise是什么?为什么会有这个东西? 首先说明,Promise是为了解决javascript异步编 ...
- Javascript Promise入门
是什么? https://www.promisejs.org/ What is a promise? The core idea behind promises is that a promise r ...
- JavaScript Promise API
同步编程通常来说易于调试和维护,然而,异步编程通常能获得更好的性能和更大的灵活性.异步的最大特点是无需等待."Promises"渐渐成为JavaScript里最重要的一部分,大量的 ...
- 【JavaScript】JavaScript Promise 探微
http://www.html-js.com/article/Promise-translation-JavaScript-Promise-devil-details 原文链接:JavaScript ...
随机推荐
- 拯救SQL Server数据库事务日志文件损坏的终极大招
拯救SQL Server数据库事务日志文件损坏的终极大招 在数据库的日常管理中,我们不可避免的会遇到服务器突然断电(没有进行电源冗余),服务器故障或者 SQL Server 服务突然停掉, 头大的是l ...
- [rCore学习笔记 08]内核第一条指令
了解QEMU 启动指令 qemu-system-riscv64 \ -machine virt \ -nographic \ -bios ../bootloader/rustsbi-qemu.bin ...
- fasterWhisper和MoneyPrinterPlus无缝集成
MoneyPrinterPlus之前使用的是各种云厂商的语音识别服务来进行语音的视频和字幕的识别工作. 但是很多小伙伴说云服务用不起. 那么没办法,MoneyPrinterPlus上线最新版本,支持f ...
- java面试一日一题:rabbitMQ如何保证消息不丢失
问题:请讲下rabbitMQ如何保证消息不丢失 分析:该问题属于概念题,同时也是一个设计方面的题,牵扯到部分设计层面的东西: 回答要点: 主要从以下几点去考虑, 1.rabbitMQ在保证消息不丢失方 ...
- fragment基础
XML中调用fragment 属性包括: android:id="@+id/fragg" //ID android:name="com.example.subway.fr ...
- hadoop web界面
通过界面监控大数据平台运行状态 通过界面查看大数据平台状态 通过大数据平台 Hadoop 的用户界面可以查看平台的计算资源和存储资源.打开 http://master:8088/cluster/nod ...
- 【Vue2】Filter 过滤器
过滤器案例: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UT ...
- 从.net开发做到云原生运维(五)——云原生时代绕不开的Kubernetes
1. 前言 前面的几篇文章主要是讲.net技术栈里的web开发技术,只是单纯的开发,从一个简单的项目到最后的打包成镜像进行分发. Kubernetes算是开启了一个新时代. 2. Kubernetes ...
- Google的TPU的Pallas扩展功能支持的数据类型
地址: https://jax.readthedocs.io/en/latest/pallas/tpu.html jnp.float32 jnp.bfloat16 jnp.int* (all prec ...
- 阿里提供的免费DNS服务器
阿里提供的免费DNS服务器的介绍网页: https://developer.aliyun.com/mirror/DNS nameserver 223.5.5.5 nameserver 223.6.6. ...