手把手教你使用ts 一步一步的去完成一个Promise
栏目: JavaScript · 发布时间: 6年前
内容简介:笔者公司前端小组,线上出现了因为Promise用错了而导致的问题。 本文的出发点不仅是了解Promise,主要目的是跟着特性去写一个Promise。 所以前提是你已经掌握了Promise的基本特性,如果没有接触过的话,请点击学习内部三种状态分别为pending、fullfiled、rejected,初始化状态为pending。状态变化可以从pending转化fullfiled或rejected。无其他转化方式。解决ajax回调地狱。
前奏
笔者公司前端小组,线上出现了因为Promise用错了而导致的问题。 本文的出发点不仅是了解Promise,主要目的是跟着特性去写一个Promise。 所以前提是你已经掌握了Promise的基本特性,如果没有接触过的话,请点击学习
正文
熟悉es6的Promise特性。
1.特性概览
-
链式调用。
-
内部三种状态。
内部三种状态分别为pending、fullfiled、rejected,初始化状态为pending。状态变化可以从pending转化fullfiled或rejected。无其他转化方式。
2.出现的背景
解决ajax回调地狱。
ajax回调地狱,本质是希望不可控制的异步变成同步形式。如:
ajax1({...,success(res1){
...
}})
ajax2({...,params:{res1},success(res2){ // error no res1
...
}})
复制代码
当我们的ajax2需要用到ajax1的时候,我们不得不使用嵌套式:
ajax1({...,success(res1){
ajax2({...,params:{res1},success(res2){
/// doing something
}})
}})
复制代码
这种写法的最大问题就是当嵌套层数很多当时候,代码会变得难以维护。
那么 how is Promise的写法 ajax改造:
p1 = function() {
return new Promise(function(resolve){
ajax1({...,success(res1){
resolve(res1)
}})
})
}
p2 = function(){
return new Promise(function(resolve){
ajax2({...,params:{res1},success(res2){
/// doing something
}})
})
}
复制代码
那么最终的写法则变成
p1().then(res=>{
return p2()
}).then(res2=>{
// doing something
})
复制代码
3 具体特性。
- 1.了解特性首推官方的Promise A+,点击
- 2.然后是用法,阮一峰大大的es6 Promise特别详细。点击
这里根据Promise A+ 把接下来的几个定义函数做在约定。
1.new Promise().then(),传递的函数分别叫 onFulfilled、onRejected
开始手写
1.Promise是个函数
所以我们最先开始的就是传递一个函数
function Promise(executor) {
if( !isFunc(executor) ){
throw 'Promise2 传递的参数不为functon!!!';
}
}
复制代码
2.Promise 初始状态。
promise有三种状态,可以用ts的枚举
enum pStatus {
pending = 'pending',
fulled = 'fullfilled',
rejected = 'rejected'
}
复制代码
然后我们需要定义一些属性。
function Promise() {
if( !isFunc(executor) ){
throw 'Promise2 传递的参数不为functon!!!';
}
this.status = pStatus.pending; // 默认状态
this.resovlecbs = []; // 回调的resolve函数 主要来自于Promise.prototype.then
this.rejectcbs = []; // 回调的reject函数 主要来自于Promise.prototype.then
this.value; // 记录的resolve值
this.error; // 记录的reject值
}
复制代码
我们知道Promise传递的函数,是直接会在主线程的执行的,所以我们需要直接执行它。
function Promise() {
if( !isFunc(executor) ){
throw 'Promise2 传递的参数不为functon!!!';
}
this.status = pStatus.pending; // 默认状态
this.resovlecbs = []; // 回调的resolve函数 主要来自于Promise.prototype.then
this.rejectcbs = []; // 回调的reject函数 主要来自于Promise.prototype.then
this.value; // 记录的resolve值
this.error; // 记录的reject值
try {
executor(resolve,reject); // 传递的函数的执行。
} catch (error) {
reject(error); // 捕获的异常会直接执行reject。
}
}
复制代码
3.开始Promise的链式结构,达到异步结果变同步,解决回调地狱。
首先需要说明的是链式的结构的原理是不断返回新的Promise,也就是说then的结果是
Promise.prototype.then = function() {
return new Promise(function(resolve,reject){
xxx...
})
}
复制代码
首先我们需要明确,Promise具体特性是什么?
1.then 传入的值分别是 resolve的回调和 reject状态回调。
2.传递值,将上一个then的值一直往下传。
3.符合同层先来先到,异层必定上层先执行的策略。
为了了解第三个特性的详细意思之前,让我们看一个例子:
var p1 = new Promise(function(resolve,reject){
resolve('p1')
});
var p2 = new Promise(function(resolve,reject){
resolve('p2')
});
p1.then(()=>{
console.log('p11')
}).then(()=>{
console.log('p12')
})
p2.then(()=>{
console.log('p21')
}).then(()=>{
console.log('p22')
})
复制代码
相信大家都知道顺序为 p11 => p21 => p12 => p22。原因的话涉及到宏微任务的特性,请参考这篇文章,点击学习。 想必大家已经明白第三点了。
那么如何实现?
分步骤:
1) 传递给Promise的函数,完成后我们才会执行then传递的函数。也就是
new Promise(function(resolve,reject){
resolve('xxx') // (1)只有执行完这个才会执行后面then的 onFulfilled函数
}).then(function(){
...xxx // (2)这是第二步
})
复制代码
所以then传递onFulfilled函数和onRejected函数都是在resolve中执行的。所以then其实只是去保存then传递的函数而已,而保存的地方则是Promise主函数内部的resolvecbs和rejectcbs这两个数组。
我觉得可能会有人问为什么会是数组?
因为你可能会这么写:
var p = new Promise(...);
p.then(function(){ ... },...);
p.then(function(){ ... },...);
p.then(function(){ ... },...);
复制代码
这种非链路,其实都是把onFullfilled,保存到Promise内部,所以需要数组。
然后就是Promise内部到resolve函数和reject函数。这两个函数会做为 用户传入的函数的参数传入。 本质内部就是去遍历执行reslovecbs的函数项。并且改变状态,还有就是将传入的值记录下来,这些值会传给onFullfilled,并由onFullfilled决定是否要继续传递下去。也即是:
then(function onFullfilled(value){ // value 来自于resolve传递的参数。
return value // return 则表示续传 下一个then是否能拿到。
})
复制代码
我们添加下,大致如下:
function Promise() {
if( !isFunc(executor) ){
throw 'Promise2 传递的参数不为functon!!!';
}
this.status = pStatus.pending; // 默认状态
this.resovlecbs = []; // 回调的resolve函数 主要来自于Promise.prototype.then
this.rejectcbs = []; // 回调的reject函数 主要来自于Promise.prototype.then
this.value; // 记录的resolve值
this.error; // 记录的reject值
const resolve = (value:object)=>{ // resolve做的三件事
this.value = value; // 记录值 then 的 onFullfilled会用它
this.resovlecbs.forEach((item:Function)=>{
item(value); // 这个就是 onFullfilled函数,会用上面的value
})
this.status = pStatus.fulled; // 把状态改变为 fullfilled
}
// ... reject同理
try {
executor(resolve,reject);
} catch (error) {
reject(error);
}
}
复制代码
resolve中执行的是resolveCbs数组存放的函数。而这些函数是来自于then推送的。 但是值得注意的是,函数除了执行then传递的onFullfiled函数和onRejected函数,还要将这两个返回的值,传递下去,所以要执行下一个Promise的resolve ,因为resolve的第一个特性就是记录值。 所以then是这样的。
Promise.prototype.then = function (onFullfilled:Function=noop,onRejected:Function=noop) {
let scope = this;
return new Promise(function(resolve = noop,reject = noop){
scope.resovlecbs.push((value)=>{
handlerRes(onFullfilled,value,resolve);
})
scope.rejectcbs.push((error)=>{
handlerRes(onRejected,error,reject);
})
});
}
export function handlerRes(handler,message,next){
let res
if(isFunc(handler)){
res = handler(message);
}
next(res); // 执行下一个函数的resolve
}
复制代码
可以看到这里是把 then传递的函数onFullfilled和onRejected分别推入 实例的 resovlecbs 数组和 rejectcbs()达到resolve和onFullfilled的同步执行的效果。
且不仅是onRresolved被执行,同时被执行的还有下一个Promise的 resolve。
这样已经实现了then 链的顺序执行了。
对于构造函数new Promise(),的几个步骤是 创建一个空对象,并将Promise内部执行的所有属性都挂载到这个对象上。也就是this的所有属性。
传递都效果图如下:
但是上面的写法会有两个问题:
-
1.无法达到前面说的
3.符合同层先来先到,异层必定上层先执行的策略。,这种效果,正式event loop的队列。 所以我们可以使用微任务或宏任务。 这里是了简化代码结构使用setTimeout来模拟,如果感兴趣可以去了解下这个npm库asap, 点击这里 -
2.目前我们的then函数的写法是直接把函数推入到resolvecbs数组,等待resolve去执行,但是这种方式不hack,如果我们先执行了resolve后,我们在执行then。比如:
var p1 = new Promise(function(resolve,reject){
resolve('p1')
});
p1.then(()=>{
console.log('p11')
}).
复制代码
这时候我们会先执行resolve, 完成了resolvecbs的遍历执行,然后才去通过then,对resolvecbs进行搜集。name后面搜集的函数就永远不会执行了。所以我们必须判断状态。
hack写法:
Promise.prototype.then = function (onFullfilled:Function=noop,onRejected:Function=noop) {
let scope = this;
return new Promise(function(resolve = noop,reject = noop){
if(scope.status === pStatus.pending) { // pending则等待执行
scope.resovlecbs.push((value)=>{
handlerRes(onFullfilled,value,resolve);
})
scope.rejectcbs.push((error)=>{
handlerRes(onRejected,error,reject);
})
} else if(scope.status===pStatus.fulled) { // fullfilled则直接执行
handlerRes(onFullfilled,scope.value,resolve);
} else { // rejectd 直接执行
handlerRes(onRejected,scope.error,reject);
}
});
}
复制代码
Promise是微任务,这里为了方便, 对Promise本身添加宏任务间隔。reject同理。
function Promise(executor:any) {
if( !isFunc(executor) ){
throw 'Promise2 传递的参数不为functon!!!';
}
this.status = pStatus.pending;
this.resovlecbs = [];
this.rejectcbs = [];
this.value;
this.error;
const resolve = (value:object)=>{
this.value = value;
setTimeout(()=>{
this.resovlecbs.forEach((item:Function)=>{
item(value);
})
this.status = pStatus.fulled;
},0)
}
const reject = (error:Error)=>{
this.error = error;
setTimeout(()=>{
this.status = pStatus.rejected;
if(this.rejectcbs.length ===0){
throw this.error;
} else {
this.rejectcbs.forEach((item:Function)=>{
item(error);
})
}
},0)
// if(this.rejectcbs.length === 0 ) throw error;
}
try {
executor(resolve,reject);
} catch (error) {
reject(error);
}
}
复制代码
然而这依然并非是最终版本,因为这无法解决,多次resolve会重复执行 resolvecbs的问题。 所以resolve函数的内容必须旨在pending的状态下才执行。 比如有人会这么做:
new Promise(function(resolve){
reslove('ddd')
resolve('ttt')
}).then(value=>{
console.log(value)
})
复制代码
为了只打印一个值,我们必须要在resolve函数做个判断,只有pending的时候会
function Promise(executor:any) {
if( !isFunc(executor) ){
throw 'Promise2 传递的参数不为functon!!!';
}
this.status = pStatus.pending;
this.resovlecbs = [];
this.rejectcbs = [];
this.value;
this.error;
const resolve = (value:object)=>{
setTimeout(()=>{
if(this.status===pStatus.pending){ // 避免重复执行。
this.value = value;
this.resovlecbs.forEach((item:Function)=>{
item(value);
})
this.status = pStatus.fulled; // 状态改变
}
},0)
}
const reject = (error:Error)=>{
setTimeout(()=>{ // why
if(this.status===pStatus.pending){ // 添加了判断 避免重复执行
this.error = error;
this.status = pStatus.rejected; //状态改变
if(this.rejectcbs.length ===0){
throw this.error;
} else {
this.rejectcbs.forEach((item:Function)=>{
item(error);
})
}
}
},0)
// if(this.rejectcbs.length === 0 ) throw error;
}
try {
executor(resolve,reject);
} catch (error) {
reject(error);
}
}
复制代码
4.Promise的catch和finally函数。
catch函数和finally函数其实是语法糖,我们完全可以用then替代的。读者大大们思考下。。
下面给出代码:
Promise.prototype.catch = function(catchcb:Function) {
return this.then(undefined, catchcb); // 本质是then
}
Promise.prototype.finally = function (callback) {
return this.then((value)=>{ // 本质是then
callback();
return value;
},callback);
}
复制代码
所以下面这种写法
p.then(onResolve,onReject).catch(onCatch).finally(onFinal); 复制代码
其实是等于
p.then(onResolve,onReject).then(undefined,onCatch).then(onFinal,onFinal); 复制代码
5.Promise.resolve。
阮一峰给出了这个函数的四种处理方式。
- 1.传递的是Promise,
- 2.传递的是thenable的对象 如 { then:function(){} }
- 3.传递是非thenbale的值
- 4.什么也没传。
需要注意的是Promise.resolve,传递出来的一定是promise。 笔者的写法是
Promise.resolve = function(handler){
if( isObject(handler) && 'constructor' in handler && handler.constructor=== this) { // handler 是 Promise
return handler;
} else if (isObject(handler) && isFunc(handler.then) ){ // thenable
return new this(handler.then.bind(handler));
} else { // 非thenable
return new this(function(resolve){
resolve(handler);
})
}
}
复制代码
可以看到如果是:
情况1,则直接原封不动的返回。
情况2则返回一个Promise,且把对象的then函数,作为参数传递进入Promise。
情况3 直接把handler resolve掉。
6.Promise.reject.
Promise.reject可不像resolve这么麻烦。完全把传递的值直接传递出来。
Promise.reject = function() {
const args = Array.prototype.slice.call(arguments);
return new this((resolve, reject) => reject(args.shift()));
}
复制代码
6.Promise.all。
首先是用法:
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return new Promise(function(resolve,reject){
setTimeout(()=>{
resolve(id);
},id);
})
});
Promise.all(promises).then(function (posts) {
console.log(posts);
})
复制代码
首先传入的是数组。其次就是所有的数组,其次就是数组存在的Promise全部都执行完后才会进入all的 then函数中。 所以需要一个标记记录实时记录所有已经完成的promise。
然后就是传入的数组 可能有promise也有可能传递的并非是Promise,所以需要hack。 区分是否存在then函数。
Promise.all = function(arr) {
if( !isArray(arr) ){
throw 'all函数 传递的参数不为Array!!!';
}
let args = Array.prototype.slice.call(arr);
let resArr = Array.call(null,Array(arr.length)).map(()=>null); // 记录所有的结果
let handlerNum = 0; // 处理标记
return new this((resolve,reject)=>{
for(let i = 0;i<args.length;i++){
let ifunc = args[i];
if(ifunc && isFunc(ifunc.then) ) { //是否存在then函数。
ifunc.then(value=>{
resArr[i] = value;
handlerNum ++; // 标记添加
if(handlerNum>=arr.length){ // 彻底完成
resolve(resArr) // 完成后的数组
}
},error=>{
reject(error);
});
} else { // 非thenable
resArr[i] = ifunc;
handlerNum ++; // 标记添加
if(handlerNum>=arr.length){ // 彻底完成
resolve(resArr) // 完成后的数组
}
}
}
});
}
复制代码
7. Promise.race。
直接上代码吧,大致就是跑的最快的会作为结果传回
Promise2.race = function(arr) {
if( !isArray(arr) ){
throw 'race函数 传递的参数不为Array!!!';
}
let args = Array.prototype.slice.call(arr);
let hasResolve = false;
return new this((resolve,reject)=>{
for(let i = 0;i<args.length;i++){
let ifunc = args[i];
if(ifunc && isFunc(ifunc.then) ) {
ifunc.then(value=>{
!hasResolve && resolve(value)
},error=>{
!hasResolve && reject(error);
});
} else {
hasResolve = true;
!hasResolve && resolve(ifunc)
}
}
})
}
复制代码
分析同事的问题。
源代码大致如下
let test = function() {
return new Promise((resolve,reject)=>{
reject(new Error('test'))
})
}
Promise.resolve('new').then(res=>{
test().then(res2=>{
...
})
}).catch(err=>{
// use err
console.log(err)
})
复制代码
遇到的问题是,最后的catch里面拿不到err。
文中我们已经说过,catch只是then的语法糖,而then的值的传递,是靠onFullfilled的return 和 onRejected的return 传递了。问题这是在then里面缺少了return。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 使用geohash完成地理距离计算
- 使用装饰器完成用户身份验证
- 使用 Reactor 完成类似 Flink 的操作
- 使用REST规范从未完成的5件事
- 使用 Reactor 完成类似的 Flink 的操作
- 使用 Bootstrap Token 完成 TLS Bootstrapping
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
ActionScript 3.0 Cookbook
Joey Lott、Darron Schall、Keith Peters / Adobe Dev Library / 2006-10-11 / GBP 28.50
Well before Ajax and Microsoft's Windows Presentation Foundation hit the scene, Macromedia offered the first method for building web pages with the responsiveness and functionality of desktop programs......一起来看看 《ActionScript 3.0 Cookbook》 这本书的介绍吧!