# Promise
```javascript
var promise = new Promise(function(resolve,reject){
//code
})
```
## 1.then
then方法可以接受两个回调函数作为参数,第一个回调为promise状态为resolve时触发,另一个为reject时触发。
// 调用顺序
```javascript
let promise = new Promise((resolve,reject)=>{
console.log('Promise init');
resolve();
})
promise.then(()=>console.log('then is called'));
console.log('hi')
```
这里输出情况为
```
Promise init
hi
then is called
```
resolve中返回promise的情况
```html
<script>
var p1 = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('fail')), 3000);
});
var p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve(p1), 1000)
})
p2.then(result => console.log(result))
.catch(error => console.log(error))
</script>
```
这里p2在1秒钟后返回promise类型p1 从这时起,p2的状态就取决于p1 所以再经过两秒,即三秒钟后,触发p1的reject 由p2的catch获取。
## 2.catch
用于返回rejecte回调 error处理。等同于
```javascript
.then(null,callback);
```
## 3.all
将多个promise实例包装成一个新的promise实例。当所有promise为resolve时,会返回then 只要有一个rejecte,为catch。
```javascript
var p = Promise.call([p1,p2,p3]);
```
## 4.race
若有一个实例率先改变状态,p的状态就跟着改变。
```javascript
var p = Promise.race([p1,p2,p3]);
```
## 5.resolve
### a.参数是一个thenable对象:
```javascript
let thenable = {
then: (resolve, reject) => resolve(42)
};
let p1 = Promise.resolve(thenable);
p1.then((value) => console.log(value));
```
如果是一个thenable对象,resolve将该对象转换为promise对象并立即执行该对象的then方法 输出42.
### b.参数不是具有then方法的对象或者根本不是对象:
```javascript
let p = Promise.resolve('Hello');
p.then(s => console.log(s));
```
这里判断p并不是一个thenable对象,所以将p转换为promise对象,且初始状态就是resolve,立即执行then方法 输出Hello。
### c.不带有任何参数:
在不带有任何参数传入时,直接在本轮循环结束时生成一个promise对象。
如何定义本轮循环结束:
```javascript
setTimeout((() => console.log('three')), 0);
Promise.resolve().then(() => console.log('two'));
console.log('one');
```
setTimeout(fn, 0)在下一轮“事件循环”开始时执行。所以可知输出顺序为:
```
one
two
three
```
## 例题
> 1. 如何按顺序调用promise
>
需要注意的是,promise在创建时就会调用。所以想要顺序执行promise,那就顺序创建。所以下面方法可以顺序执行promise。
~~~
function promiseFactory(index){
return new Promise((resolve, reject) => {
resolve()
console.log(index)
})
}
function executePromises(promisesIndex){
var result = Promise.resolve()
promisesIndex.forEach(index => {
result = result.then(promiseFactory(index))
})
return result
}
executePromises([1,2,3,4])//1,2,3,4
~~~
假设我们现在有这三个promise:
~~~
var a = function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('a')
}, 1000)
})
}
// 异步函数b
var b = function (data) {
return new Promise(function (resolve, reject) {
resolve(data + 'b')
})
}
// 异步函数c
var c = function (data) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(data + 'c')
}, 500)
})
}
~~~
执行时,这样就可以:
~~~
var a = function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('a')
}, 2000)
})
}
// 异步函数b
var b = function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('b')
}, 1000)
})
}
// 异步函数c
var c = function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('c')
}, 500)
})
}
let ret = [];
function getTarget(data) {
ret.push(data);
}
function queue(arr) {
var sequence = Promise.resolve() // 定义resolve状态的promise 后续组成promise.then队列。
arr.forEach(function (item) {
sequence = sequence.then(item).then(getTarget)
});
return sequence
}
// 执行队列
queue([a, b, c]).then(data => {
console.log(ret);
});
~~~
> 2. 不同状态输出不同answer
>
**sol**
~~~
const proposeToMissHan = (isOK) => {
/* TODO */
const reply = new Promise((resolve, reject) => {
const thinkTime = 35;
setTimeout(() => {
if (isOK) {
resolve('yes')
} else {
reject('no');
}
}, thinkTime)
})
return reply;
}
proposeToMissHan(true).then((res)=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
~~~
> 3. 使用 generator 模拟 async/await
> 在远古时代,我们使用 callback 进行异步流程控制,但是会有 callback hell 的问题。经过历史的发展,逐渐地使用了不少的工具进行异步流程的改进,例如 Async.js、Promise 等,到后来的 generator + promise,还有最终的方案 async/await。了解以前是用什么方案处理异步流程控制,对我们理解现在的 asyn/await 也是很有好处。
请你实现一个简单的函数 wrapAsync,使用 generator + promise 来模拟 async/await 进行异步流程的控制。wrapAsync 接受一个 generator 函数作为参数,并且返回一个函数。generator 函数内部可以使用关键字 yield 一个 Promise 对象,并且可以类似 async/await 那样获取到 Promise 的返回结果,例如:
const getData = (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('My name is ' + name)
}, 100) // 模拟异步获取数据
})
}
const run = wrapAsync(function * (lastName) {
const data1 = yield getData('Jerry ' + lastName)
const data2 = yield getData('Lucy ' + lastName)
return [data1, data2]
})
run('Green').then((val) => {
console.log(val) // => [ 'My name is Jerry Green', 'My name is Lucy Green' ]
})
getData 是一个异步函数并且返回 Promise,我们通过 yield 关键字获取到这个异步函数的 Promise 返回的结果,在代码编写上起来像是同步的,执行上实际是异步的。
请你完成 wrapAsync 的编写,wrapAsync 返回的函数接受的参数和传入的 generator 接受的函数保持一致,并且在调用的时候会传给 generator 函数(正如上面的例子);另外,wrapAsync 返回的函数执行结果是一个 Promise,我们可以通过这个 Promise 获取到 generator 函数执行的结果(正如上面的例子)。
(此简单实现你暂时不需要考虑异常的控制。)
>
**sol**
~~~
const wrapAsync = (generatorFn) => {
return (...args) => {
const gen = generatorFn.apply(null, args);
return new Promise((resolve, reject) => {
let g = null;
const fullFill = (res) => {
g = gen.next(res);
next(g);
};
fullFill();
function next(ret) {
if (ret.done) return resolve(ret.value);
return ret.value.then(fullFill);
}
})
}
}
~~~
## promise实现原理
### 1. macrotask microtask
macrotask microtask 分别表示异步任务的两种分类。在挂起任务时,JS 引擎会将所有任务按照类别分到两个队列中,首先在 macrotask 的队列(也叫 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。
### 2. 理解 Promise 实现过程要点:
1. then方法中的回调需要异步延迟调用。虽然 Promise A+ 未明确指出是以 macrotask(task - 宏任务) 还是 microtask(微任务) 形式放入队列,但 ECMAScript 规范明确指出 Promise 必须以 Promise Job 形式加入 job queues(也就是 microtask)。
2. Promise 的调用方式是同步链式调用,因此在第一个异步任务执行前,链式调用环节产生的多个 Promise 实例便已经生成(每次 then 方法会生成一个新的 Promise 实例),并维护自己的一些内部变量(状态变量,回调处理队列,执行结果)
3. 每个 Promise 实例自身维护一个 回调处理队列,该队列存储着其下一个要调用的 then 方法相关信息,包括这个 then 方法接受的回调函数(resolve / reject)和这个 then 方法返回的新 Promise 实例。队列中可能不止包括一个元素,因为规范描述:then 方法可以被同一个 Promise 调用多次
4. 为使 Promise 的实现更具有通用性, 根据规范,传入 Promise 构造函数的 resolve 函数接受多种类型参数。
### 3. promise 构造函数
~~~
function Promise(fn) {
this._state = 0 // 0-pending 1 resolve 2 reject
this._value - null // promise 运行结果
this._deferreds = [] // 注册回调数组
try {
fn(value => {
resolve(this, value)
}, reason => {
reject(this, reason)
})
}
catch (err) {
reject(err)
}
}
~~~
### 4.then方法的实现
then 方法可以被同一个 Promise 调用多次,每次返回新 Promise 对象 。then 方法接受两个参数 onResolved、onRejected(可选)。在 Promise 被 resolve 或 reject 后,所有 onResolved 或 onRejected 函数须按照其注册顺序依次回调,且调用次数不超过一次。所以then的主要流程有一下几步:
1. 实例化空promise对象用于返回。
2. 构造then注册回调处理函数结构体
3. 判断当前状态,pending状态存储延迟处理对象,即this._deferreds.push,非pending状态执行onResolve或onReject操作。
实现如下
~~~
function Handler(onResolve, onReject, promise) { // 封装存储onResolve onReject对象,生成新promise对象
this.onResolved = typeof onResolve === 'function' ? onResolve : null;
this.onRejected = typeof onReject === 'function' ? onReject : null;
this.promise = promise;
}
Promise.prototype.then = function (onResolve, onReject) {
var res = new Promise(function () { });
var deferred = new Handler(onResolve, onReject, res);
if (this._state === 0) {
this._deferreds.push(deferred);
return res;
}
handleResolve(this, deferred);
return res;
}
~~~
> 链式调用为什么要返回新的 promise
如我们理解,为保证 then 函数链式调用,then 需要返回 promise 实例。但为什么返回新的 promise,而不直接返回 this 当前对象呢?看下面示例代码:
var promise2 = promise1.then(function (value) {
return Promise.reject(3)
})
复制代码假如 then 函数执行返回 this 调用对象本身,那么 promise2 === promise1,promise2 状态也应该等于 promise1 同为 resolved。而 onResolved 回调中返回状态为 rejected 对象。考虑到 Promise 状态一旦 resolved 或 rejected就不能再迁移,所以这里 promise2 也没办法转为回调函数返回的 rejected 状态,产生矛盾。
### 5.catch 的实现
catch是then(null,onReject)的别名
~~~
Promise.prototype['catch'] = function (onRejected) {
return this.then(null, onRejected);
};
~~~
### 6.resolve函数
Promise 实例化时立即执行传入的 fn 函数,同时传递内部 resolve 函数作为参数用来改变 promise 状态。resolve 函数简易版逻辑大概为:判断并改变当前 promise 状态,存储 resolve(..) 的 value 值。判断当前是否存在 then(..) 注册回调执行函数,若存在则依次异步执行 onResolved 回调。
但如文初所 thenable 章节描述,为使 Promise 的实现更具有通用性,当 value 为存在 then(..) 方法的 thenable 对象,需要做 Promise Resolution Procedure 处理,规范描述为 [[Resolve]](promise, x)。(x 即 为后面 value 参数)。
具体处理逻辑流程如下:
* 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
* 如果 x 为 Promise ,则使 promise 接受 x 的状态
* 如果 x 为对象或函数
1. 把 x.then 赋值给 then
2. 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
3. 如果 then 是函数,将 x 作为函数的作用域 this 调用之。
4. 如果 x 不为对象或者函数,以 x 为参数执行 promise。
~~~
function resolve (promise, value) {
// 非 pending 状态不可变
if (promise._state !== 0) return;
// promise 和 value 指向同一对象
// 对应 Promise A+ 规范 2.3.1
if (value === promise) {
return reject( promise, new TypeError('A promise cannot be resolved with itself.') );
}
// 如果 value 为 Promise,则使 promise 接受 value 的状态
// 对应 Promise A+ 规范 2.3.2
if (value && value instanceof Promise && value.then === promise.then) {
var deferreds = promise._deferreds
if (value._state === 0) {
// value 为 pending 状态
// 将 promise._deferreds 传递 value._deferreds
// 偷个懒,使用 ES6 展开运算符
// 对应 Promise A+ 规范 2.3.2.1
value._deferreds.push(...deferreds)
} else if (deferreds.length !== 0) {
// value 为 非pending 状态
// 使用 value 作为当前 promise,执行 then 注册回调处理
// 对应 Promise A+ 规范 2.3.2.2、2.3.2.3
for (var i = 0; i < deferreds.length; i++) {
handleResolved(value, deferreds[i]);
}
// 清空 then 注册回调处理数组
value._deferreds = [];
}
return;
}
// value 是对象或函数
// 对应 Promise A+ 规范 2.3.3
if (value && (typeof value === 'object' || typeof value === 'function')) {
try {
// 对应 Promise A+ 规范 2.3.3.1
var then = obj.then;
} catch (err) {
// 对应 Promise A+ 规范 2.3.3.2
return reject(promise, err);
}
// 如果 then 是函数,将 value 作为函数的作用域 this 调用之
// 对应 Promise A+ 规范 2.3.3.3
if (typeof then === 'function') {
try {
// 执行 then 函数
then.call(value, function (value) {
resolve(promise, value);
}, function (reason) {
reject(promise, reason);
})
} catch (err) {
reject(promise, err);
}
return;
}
}
// 改变 promise 内部状态为 `resolved`
// 对应 Promise A+ 规范 2.3.3.4、2.3.4
promise._state = 1;
promise._value = value;
// promise 存在 then 注册回调函数
if (promise._deferreds.length !== 0) {
for (var i = 0; i < promise._deferreds.length; i++) {
handleResolved(promise, promise._deferreds[i]);
}
// 清空 then 注册回调处理数组
promise._deferreds = [];
}
}
~~~
### 7. reject实现
~~~
function reject (promise, reason) {
// 非 pending 状态不可变
if (promise._state !== 0) return;
// 改变 promise 内部状态为 `rejected`
promise._state = 2;
promise._value = reason;
// 判断是否存在 then(..) 注册回调处理
if (promise._deferreds.length !== 0) {
// 异步执行回调函数
for (var i = 0; i < promise._deferreds.length; i++) {
handleResolved(promise, promise._deferreds[i]);
}
promise._deferreds = [];
}
}
~~~
### 8. handleResolved
handleResolved 函数具体会根据 Promise 实例当前状态判断调用 onResolved、onRejected 中的一个,处理 then(..) 注册回调为空情形,以及维护链式 then(..) 函数后续调用。
~~~
function handleResolved (promise, deferred) {
// 异步执行注册回调
asyncFn(function () {
var cb = promise._state === 1 ? deferred.onResolved : deferred.onRejected;
// 传递注册回调函数为空情况
if (cb === null) {
if (promise._state === 1) {
resolve(deferred.promise, promise._value);
} else {
reject(deferred.promise, promise._value);
}
return;
}
// 执行注册回调操作
try {
var res = cb(promise._value);
} catch (err) {
reject(deferred.promise, err);
}
// 处理链式 then(..) 注册处理函数调用
resolve(deferred.promise, res);
});
}
~~~
## Promise的相关API实现
### resolve
> ① 参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
② 参数是一个thenable对象
thenable对象指的是具有then方法的对象,比如下面这个对象,例如:
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
③ 参数不是具有then方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
④ 不带有任何参数
Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
~~~
function valuePromise(value) {
var p = new Promise(function(){});
p._state = 1;
p._value = value;
return p;
}
Promise.resolve = function (value) {
if (value instanceof Promise) return value;
if (typeof value === 'object' || typeof value === 'function') {
try {
var then = value.then;
if (typeof then === 'function') {
return new Promise(then.bind(value));
}
} catch (ex) {
return new Promise(function (resolve, reject) {
reject(ex);
});
}
}
return valuePromise(value);
};
~~~
### Promise.reject
> Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
~~~
Promise.reject = function (value) {
return new Promise(function (resolve, reject) {
reject(value);
});
};
~~~
### Promise.race
`const p = Promise.race([p1, p2, p3]);`
> Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
~~~
Promise.race = function (values) {
return new Promise(function (resolve, reject) {
values.forEach(function(value) {
Promise.resolve(value).then(resolve, reject);
});
});
};
~~~
- html
- 冒泡/捕获/委托
- 前端路由
- Dom
- 创建节点API
- 页面修改型API
- 节点查询型API
- 节点关系型API
- 元素属性API
- DOM事件
- classList
- 性能优化
- 节流防抖
- localStorage sessionStorage
- BOM
- meta
- data属性
- js实现拖拽
- html5
- 关于meta
- 轮播图
- js实现拖放
- 电话号inputFormater
- js
- es6
- promise
- iterator
- generator
- async
- proxy
- Set
- Map
- Object的扩展
- String
- Iterator
- Symbol
- 解构赋值
- 函数式编程
- module
- 基本数据类型
- 数组相关codings
- for of/for in
- this
- call bind apply
- 闭包
- 作用域
- prototype与继承
- 深浅拷贝
- setTimeOut/setInterval
- 垃圾处理机制
- 设计模式
- 观察者模式
- 单例模式
- 策略模式
- RegExp
- with
- 其他玩意
- Error/Stack Trace
- 面向对象
- css
- 回流重绘
- %取值
- 属性继承/属性优先级
- flex
- BFC
- 盒模型
- 设置css的方法
- 定位机制
- 块级/行内元素
- hack和一些别的玩意
- css动画
- 几个布局
- 画图形
- css3
- animation对比transform
- 点击不同图片区域跳转不同
- css选择器性能问题
- vh rem em
- css选择器
- 伪类伪元素
- css匹配原理
- 数据结构与算法
- 数据结构
- 树
- 链表
- 栈和队列
- 排序
- 归并排序
- 插入排序
- 选择排序
- 冒泡排序
- 快速排序
- 递归
- 回溯法
- 搜索算法
- 动态规划
- http
- 跨域问题
- CORS
- GET/POST
- ajax
- ajax上传文件
- http缓存
- https
- TCP/UDP
- cookie/session
- http2.0
- spdy
- websocket
- React
- redux
- 生命周期
- 虚拟dom与diff
- 双向数据绑定
- mvvm
- setState
- contextApi props reudx
- 高阶组件
- react-redux
- Fiber
- react-router
- 受控/非受控组件
- 待整理
- webpack
- loader实现
- 前端安全
- 移动端适配
- Vue
- 传值
- 其他