为什么需要Promise
Javascript 是一⻔单线程语言,所以早期我们解决异步的场景时,大部分情况都是通过回调函数来进 行。
例如在浏览器中发送 ajax 请求,就是常⻅的一个异步场景,发送请求后,一段时间服务端响应之后我们 才能拿到结果。如果我们希望在异步结束之后执行某个操作,就只能通过回调函数这样的方式进行操作。
var dynamicFunc = function(cb) {
setTimeout(function() {
cb();
}, 1000);
}
dynamicFunc(function() {console.log(123)});
例如上面这个例子,这里的 dynamicFunc 就是一个异步的函数,里面执行的 setTimeout 会在 1s 之后调 用传入的 cb 函数。按照上面的调用方式,最终 1s 之后,会打印 123 这个结果。
同样的,如果后续还有内容需要在异步函数结束时输出的话,就需要多个异步函数进行嵌套,非常不利 于后续的维护。
setTimeout(function() {
console.log(123);
setTimeout(function() {
console.log(321);
// ...
}, 2000);
}, 1000);
为了能使回调函数以更优雅的方式进行调用,在 ES6 中 js 产生了一个名为 promise 的新规范,他让异步 操作的变得近乎「同步化」。
Promise基础
在支持 ES6 的高级浏览器环境中,我们通过 new Promise() 即可构造一个 promise 实例。
这个构造函数接受一个函数,分别接受两个参数,resolve 和 reject,代表着我们需要改变当前实例的状 态到 已完成 或是 已拒绝 。
function promise1() {
return new Promise(function(resolve, reject) {
// 定义异步的内容
setTimeout(function() {
console.log('1s 后输出');
// 输出完成后,调用函数传入的 resolve 函数,将该 promise 实例标记为已完成,当前 promise 串 行继续执行
resolve();
}, 1000);
}); }
function promise3() {
return new Promise(function(resolve, reject) {
var random = Math.random() * 10; // 随机一个 1 - 10 的数字
setTimeout(function() {
if (random >= 5) {
resolve(random);
} else {
reject(random);
}
}, 1000);
}); }
var onResolve = function(val) { console.log('已完成:输出的数字是', val);
};
var onReject = function(val) { console.log('已拒绝:输出的数字是', val);
}
// promise 的 then 也可以接受两个函数,第一个参数为 resolve 后执行,第二个函数为 reject 后执行
promise3().then(onResolve, onReject);
// 也可以通过 .catch 方法拦截状态变为已拒绝时的 promise
promise3().catch(onReject).then(onResolve);
// 也可以通过 try catch 进行拦截状态变为已拒绝的 promise
try {
promise3().then(onResolve);
} catch (e) {
onReject(e);
}
这个例子使用了三种方式拦截最终变为「已拒绝」状态的 promise,分别是使用 then 的第二个参数,使 用 .catch 方法捕获前方 promise 抛出的异常,使用 try catch 拦截代码块中 promise 抛出的异常 同时我们还可以发现,在改变 promise 状态时调用 resolve 和 reject 函数的时候,也可以给下一步 then 中执行的函数传递参数。这个例子中我们把随机生成的数字传给了 resolve 和 reject 函数,我们也就能在 then 中执行函数的时候拿到这个值。
- promise 会有三种状态,「进行中」「已完成」和「已拒绝」,进行中状态可以更改为已完成或 已拒绝,已经更改过状态后无法继续更改(例如从已完成改为已拒绝)。
- ES6 中的 Promise 构造函数,我们构造之后需要传入一个函数,他接受两个函数参数,执行第一 个参数之后就会改变当前 promise 为「已完成」状态,执行第二个参数之后就会变为「已拒绝」 状态。
- 通过 .then 方法,即可在上一个 promise 达到已完成时继续执行下一个函数或 promise。同时通过 resolve 或 reject 时传入参数,即可给下一个函数或 promise 传入初始值。
- 已拒绝的 promise,后续可以通过 .catch 方法或是 .then 方法的第二个参数或是 try catch 进行捕 获。
如何封装异步操作为 promise
们可以将任何接受回调的函数封装为一个 promise,下面举几个简单的例子来说明。
// 原函数
function dynamicFunc(cb) {
setTimeout(function() {
console.log('1s 后显示'); cb();
}, 1000);
}
var callback = function() {
console.log('在异步结束后 log');
}
// 用传入回调函数的方式执行
dynamicFunc(callback);
上面的例子就是最传统的,使用传入回调函数的方式在异步结束后执行函数。我们可以通过封装 promise 的方式,将这个异步函数变为 promise。
function dynamicFuncAsync() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log('1s 后显示'); resolve();
});
});
}
var callback = function() {
console.log('在异步结束后 log');
}
dynamicFuncAsync().then(function() { callback(); });
再举一个例子,发送 ajax 请求也可以进行封装:
function ajax(url, success, fail) {
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
success(this.response);
} else {
fail(new Error(this.statusText));
}
};
client.send();
};
ajax('/ajax.json', function() {console.log('成功')}, function() {console.log('失败')});
我们可以看到,调用 ajax 方法时需要传入 success 和 fail 的回调函数进行调用。我们可以不传入回调函 数,通过封装 promise 的方式,在原来的执行回调函数的地方更改当前 promise 的状态,就可以通过链 式调用。
function ajaxAsync(url) {
return new Promise(function(resolve, reject){
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
client.send();
});
};
ajaxAsync('/ajax.json')
.catch(function() {
console.log('失败');
})
.then(function() {
console.log('成功');
})
- 我们可以轻松的把任何一个函数或者是异步函数改为 promise,尤其是异步函数,改为 promise 之 后即可进行链式调用,增强可读性。
- 将带有回调函数的异步改为 promise 也很简单,只需要在内部实例化 promise 之后,在原来执行 回调函数的地方执行对应的更改 promise 状态的函数即可。
promise 规范解读
任何符合 promise 规范的对象或函数都可以成为 promise,promise A plus 规范地址: promisesaplus.com/
上面我们熟悉了整体 promise 的用法,我们知道了如何去创建一个 promise,如何去使用它,后面我们 也熟悉了如何去改造回调函数到 promise。本小节我们详细过一遍 promise A+ 规范,从规范层面明白 promise 使用过程中的细节。
promise 的状态
一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、已完成(Fulfilled)和已 拒绝(Rejected) 。
处于等待态时,promise 需满足以下条件:可以变为「已完成」或「已拒绝」
处于已完成时,promise 需满足以下条件:1. 不能迁移至其他任何状态 2. 必须拥有一个不可变的值
处于已拒绝时,promise 需满足以下条件:1. 不能迁移至其他任何状态 2. 必须拥有一个不可变的原因
必须有一个 then 方法
一个 promise 必须提供一个 then 方法以访问其当前值和原因。
promise 的 then 方法接受两个参数: promise.then(onFulfilled, onRejected) 他们都是可选参数,同时他 们都是函数,如果 onFulfilled 或 onRejected 不是函数,则需要忽略他们。
如果 onFulfilled 是一个函数
当 promise 执行结束后其必须被调用,其第一个参数为 promise 的结果 在 promise 执行结束前其不可被调用其调用次数不可超过一次
如果 onRejected 是一个函数
当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的原因 在 promise 被拒绝执行前其不可被调用其调用次数不可超过一次
在执行上下文堆栈仅包含平台代码之前,不得调用 onFulfilled 或 onRejected
onFulfilled 和 onRejected 必须被作为普通函数调用(即非实例化调用,这样函数内部 this 非严 格模式下指向 window)
then 方法可以被同一个 promise 调用多次
当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调
then方法必须返回一个 promise 对象 promise2 = promise1.then(onFulfilled, onRejected)
只要 onFulfilled 或者 onRejected 返回一个值 x ,promise 2 都会进入 onFulfilled 状态
如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回 拒因 e
如果 onFulfilled 不是函数且 promise1 状态变为已完成, promise2 必须成功执行并返回相 同的值
如果 onRejected 不是函数且 promise1 状态变为已拒绝, promise2 必须执行拒绝回调并返 回相同的据因
var promise1 = new Promise((resolve, reject) => {reject();});
promise1
.then(null, function() {
return 123;
})
.then(null, null)
.then(null, null)
.then(
() => {
console.log('promise2 已完成');
},
() => {
console.log('promise2 已拒绝');
});
//promise2 已完成
promise解决方案
Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]] (promise, x) (这句话的意思就是把 promise resolve 了,同时传入 x 作为值)
promise.then(function(x) {
console.log('会执行这个函数,同时传入 x 变量的值', x);
});
-
如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 用 x 的值来执行 promise 。
-
如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise 如果 x 为 promise
-
如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
-
如果 x 处于执行态,用相同的值执行 promise
-
如果 x 处于拒绝态,用相同的据因拒绝 promise
var promise1 = function() { return new Promise(function(resolve) { setTimeout(function() { console.log(1); resolve(); }, 1000) }); } var promise2 = function() { return new Promise(function(resolve) { setTimeout(function() { console.log(2); resolve(); }, 2000); }); } promise1() .then(function() { return promise2(); // 此处返回一个 promise 实例 }) .then(function() { console.log('已完成') }, function() { console.log('已拒绝') });- 如果 x 为 Object 或 function(不常⻅)
-
首先尝试执行 x.then
-
如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
-
如果 then 是函数,将 x 作为函数的作用域 this 调用。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise :
- 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
- 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
- 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先 采用首次调用并忽略其他的调用如果调用 then 方法抛出了异常 e
- 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略
- 否则以 e 为据因拒绝 promise
-
如果 then 不为函数,以 x 为参数将 promise 变为已完成状态
-
-
-
如果 x 不为对象或者函数,以 x 为参数将 promise 变为已完成状态(重要且常⻅)
Promise 构造函数上的静态方法
Promise.resolve
返回一个 promise 实例,并将它的状态设置为已完成,同时将他的结果作为传入 promise 实例的值
var promise = Promise.resolve(123);
promise
.then(function(val) {
console.log('已完成', val);
});
// 已完成 123
同样的, Promise.resolve 的参数也可以处理对象,函数等内容,处理方式和上面规范中介绍的相同。
Promise.reject
var promise = Promise.reject(123);
promise
.then(null, function(val) {
console.log('已拒绝', val);
});
// 已拒绝 123
Promise.all
返回一个 promise 实例,接受一个数组,里面含有多个 promise 实例,当所有 promise 实例都成为已完 成状态时,进入已完成状态,否则进入已拒绝状态。
var promise1 = function() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(1);
resolve();
}, 1000)
})
}
var promise2 = function() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(2);
resolve();
}, 2000); });
}
Promise.all([promise1(), promise2()])
.then(function() {
console.log('全部 promise 均已完成');
});
注意,此时多个 promise 是同时进行的,也就是在上面这个例子中,等待 1s 打印 1 之后,再等待 1s 就 会打印 2 和「全部 promise 均已完成」。
Promise.race
返回一个 promise 实例,接受一个数组,里面含有多个 promise 实例,当有一个 promise 实例状态改变 时,就进入该状态且不可改变。这里所有的 promise 实例为竞争关系,只选择第一个进入改变状态的 promise 的值。
var promise1 = function() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(1);
resolve(1);
}, 1000) });
}
var promise2 = function() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(2);
resolve(2);
}, 2000); });
}
Promise.race([promise1(), promise2()])
.then(function(val) {
console.log('有一个 promise 状态已经改变', val);
});
实现一个promise
//Promise 完整的实现
class Promise {
callbacks = [];
state = 'pending';//增加状态
value = null;//保存结果
constructor(fn) {
fn(this._resolve.bind(this), this._reject.bind(this));
}
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
this._handle({
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
resolve: resolve,
reject: reject
});
});
}
catch(onError) {
return this.then(null, onError);
}
finally(onDone) {
if (typeof onDone !== 'function') return this.then();
let Promise = this.constructor;
return this.then(
value => Promise.resolve(onDone()).then(() => value),
reason => Promise.resolve(onDone()).then(() => { throw reason })
);
}
static resolve(value) {
if (value && value instanceof Promise) {
return value;
} else if (value && typeof value === 'object' && typeof value.then === 'function') {
let then = value.then;
return new Promise(resolve => {
then(resolve);
});
} else if (value) {
return new Promise(resolve => resolve(value));
} else {
return new Promise(resolve => resolve());
}
}
static reject(value) {
if (value && typeof value === 'object' && typeof value.then === 'function') {
let then = value.then;
return new Promise((resolve, reject) => {
then(reject);
});
} else {
return new Promise((resolve, reject) => reject(value));
}
}
static all(promises) {
return new Promise((resolve, reject) => {
let fulfilledCount = 0
const itemNum = promises.length
const rets = Array.from({ length: itemNum })
promises.forEach((promise, index) => {
Promise.resolve(promise).then(result => {
fulfilledCount++;
rets[index] = result;
if (fulfilledCount === itemNum) {
resolve(rets);
}
}, reason => reject(reason));
})
})
}
static race(promises) {
return new Promise(function (resolve, reject) {
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then(function (value) {
return resolve(value)
}, function (reason) {
return reject(reason)
})
}
})
}
_handle(callback) {
if (this.state === 'pending') {
this.callbacks.push(callback);
return;
}
let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;
if (!cb) {//如果then中没有传递任何东西
cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
cb(this.value);
return;
}
let ret;
try {
ret = cb(this.value);
cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
} catch (error) {
ret = error;
cb = callback.reject
} finally {
cb(ret);
}
}
_resolve(value) {
if(this.state !== 'pending') return
if (value && (typeof value === 'object' || typeof value === 'function')) {
var then = value.then;
if (typeof then === 'function') {
then.call(value, this._resolve.bind(this), this._reject.bind(this));
return;
}
}
this.state = 'fulfilled';//改变状态
this.value = value;//保存结果
this.callbacks.forEach(callback => this._handle(callback));
}
_reject(error) {
if(this.state !== 'pending') return
this.state = 'rejected';
this.value = error;
this.callbacks.forEach(callback => this._handle(callback));
}
}
Promise的一些执行题目
1、
Promise.resolve()
.then(() => {
return new Error('error!!!');
})
.then(res => {
console.log('then:' + res);
})
.catch(err => {
console.log('catch:' + err);
})
输出结果: then:Error: error!!!
2、
let promise = new Promise((resolve, reject) => {
resolve('success1');
reject('error');
resolve('success2');
})
promise
.then((res) => {
console.log('then:' + res);
})
.catch((err) => {
console.log('catch:' + err);
});
输出结果:then:success1
3、
let promise = new Promise((resolve, reject) => {
console.log(1);
resolve();
console.log(2);
})
promise.then(() => {
console.log(3);
})
console.log(4);
输出结果:1 2 4 3
Promise 构造函数内部的执行器函数内部属于同步代码,.then 注册的回调函数属于微任务,那么会先输出同步代码 1,遇到 resolve() 并不会阻止后面同步代码的执行,因为并没有 return 语句。然后将微任务加入微任务队列,之后打印同步代码 2,之后继续先打印同步代码 4,最后取出微任务队列中的任务元素,打印 3,因此打印结果为 1 2 4 3。
4、
let p1 = new Promise((resolve, reject) => {
reject(42);
});
p1.catch((function (value) {
console.log(value);
return value + 1;
})).then(function (value) {
console.log(value);
});
输出结果: 42 43
5、
let p1 = new Promise((resolve, reject) => {
resolve(42);
});
let p2 = new Promise((resolve, reject) => {
reject(new Error('TypeError!!!'));
});
p1.then(function (value) {
console.log(value);
return p2;
}).then(function (value) {
console.log(value);
}, function (err) {
console.log(err);
})
输出结果: 42 Error: TypeError!!!
6、
setTimeout(() => {
console.log('timer1');
Promise.resolve().then(() => {
console.log('promise1');
})
})
Promise.resolve().then(() => {
console.log('promise2');
setTimeout(() => {
console.log('timer2');
})
})
输出结果:promise2 timer1; promise1 timer2
7、
Promise.resolve()
.then(() => { // 外层第一个then
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
})
})
.then(() => { // 外层第二个then
console.log(3);
})
输出结果: 1 3 2
先将外层第一个 then 压入微任务列表,等待这个 then 执行完返回一个 promise 之后再将外层第二个 then 压入微任务队列吧,第一个then里面的逻辑同样如此。
8、
async function async1() {
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2 end');
}
async1();
console.log(10);
输出结果: async1 end 10 async2 end
9、
async function async1() {
try {
await async2();
} catch (err) {
console.log('async1 end');
console.log(err);
}
}
async function async2() {
console.log('async2 end');
return Promise.reject(new Error('error!!!'));
}
async1();
console.log(10);
输出结果:async2 end 10 async1 end error!!!
10、
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
输出结果: script start async1 start async2 promise1 script end async1 end promise2 setTimeout
11、
let a;
const b = new Promise((resolve, reject) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
}).then(() => {
console.log('promise3');
}).then(() => {
console.log('promise4');
});
a = new Promise(async (resolve, reject) => {
console.log(a);
await b;
console.log(a);
console.log('after1');
await a;
resolve(true);
console.log('after2');
})
console.log('end');
输出结果:promise1 undefined end promise2 promise3 promise4 Promise{ pending } after1