前端异步对象的原理与使用方法

源宝导读:现今互联网的WEB网站,几乎没有不用到JS异步技术的,虽然大家经常用到,但Javascript提供的异步机制如何才能真正用好呢,可能很多开发小伙伴还有点含糊,本文将从常见的开发场景出发,系统的介绍JS异步对象的原理与使用方法。(正文约9000字,预计阅读时间10分钟)

一、promise是什么

    Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。

二、Promise的优点

2.1、解决回调导致的代码难以维护

    在使用JavaScript时,为了实现某些逻辑经常会写出层层嵌套的回调函数,如果嵌套过多,会极大影响代码可读性和逻辑,这种情况也被成为回调地狱,比如:

// 优化前
var sayhello = function (name, callback) {setTimeout(function () {console.log(name);callback();}, 1000);
}
sayhello("first", function () {sayhello("second", function () {sayhello("third", function () {console.log("end");});});
});
// 优化后
var sayhello = function (name) {return new Promise(function (resolve, reject) {setTimeout(function () {console.log(name);resolve();  //在异步操作执行完后执行 resolve() 函数}, 1000);});
}
sayhello("first").then(function () {return sayhello("second");  //仍然返回一个 Promise 对象
}).then(function () {return sayhello("third");
}).then(function () {console.log('end');
})

2.2、可读性

    Promise规范了这种异步场景的代码,相对callback的方式,更加清晰易懂,语义化更强。

2.3、可靠性

    Promise是原生支持的API,它已经被加到了JS的规范里面,在各大浏览器中的运行机制是相同的。这样就保证了它的可靠。

2.4、信任问题

    只能决议一次,决议值只能有一个,决议之后无法改变。任何then中的回调也只会被调用一次。Promise的特征保证了Promise可以解决信任问题。

三、promise的三种状态

  • pending: 初始状态,既不是成功,也不是失败状态。

  • fulfilled: 意味着操作成功完成。

  • rejected: 意味着操作失败。

    pending 状态的 Promise 对象可能会变为fulfilled 状态并传递一个值给相应的状态处理方法,也可能变为失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用。

3.1、兼容性

对于低版本不支持的浏览器可以使用开源的polyfill解决。

[https://github.com/stefanpenner/es6-promise]

3.2、基础使用方式

let myFirstPromise = new Promise(function(resolve, reject){//当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)//在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.setTimeout(function(){resolve("成功!"); //代码正常执行!}, 250);
});myFirstPromise.then(function(successMessage){//successMessage的值是上面调用resolve(...)方法传入的值.//successMessage参数不一定非要是字符串类型,这里只是举个例子console.log("Yay! " + successMessage);
}, function (errorMessage) {console.log(errorMessage);
});

四、Promise接口

4.1、Promise.prototype.then

    方法返回一个新的Promise。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。

基础使用方式

var p1 = new Promise((resolve, reject) => {resolve('成功!');// or// reject(new Error("出错了!"));
});p1.then(value => {console.log(value); // 成功!}, reason => {console.error(reason); // 出错了!});

返回新的Promise

    每次执行then之后都将返回新的Promise对象。

回调都没有定义,那么这个新的Promise状态将为原 Promise的状态

var promise = new Promise(function(resolve, reject){reject('error')
})
var promise1 = promise
promise1.then(function(){console.log('success')
}, function (rs) {console.log(rs)
})
// 输出
// error

回调中返回了新的状态,那么以新的状态为准

var promise2 = new Promise(function(resolve, reject){reject('error')
})
var promise3 = promise2.then(null, function () {return Promise.resolve('promise3-success')
})
promise3.then(function(rs){console.log(rs)
}, function (rs) {// 不会执行
})
// 输出
// promise3-success

没有定义回调,那么以原来Promise状态为准

var promise = Promise.reject('error')
promise.then(() => {// 不会执行
}, null).then(() => {// 不会执行
}, (rs) => {console.log(rs) // error
}

回调中如果返回了Error,那么新的Promise状态为rejected

var promise4 = new Promise(function(resolve, reject){resolve('success')
})
var promise5 = promise4.then(function(){throw new Error('error')
})
promise5.then(function(){}, function(rs){console.log(rs)
})
// 输出
// error

Promise可重复执行then或者catch

var promise = new Promise(function(resolve, reject){resolve('success')
})
promise.then(function(){console.log('one')
})
promise.then(function(){console.log('two')
})
promise.then(function(){console.log('three')
})
// 输出
// one
// two
// three

4.2、Promise.prototype.catch

    添加一个拒绝(rejection) 回调到当前 promise, 返回一个新的promise。当这个回调函数被调用,新 promise 将以它的返回值来resolve,否则如果当前promise 进入fulfilled状态,则以当前promise的完成结果作为新promise的完成结果。

使用链式语句的 catch方法

var p1 = new Promise(function(resolve, reject) {resolve('Success');
});
p1.then(function(value) {console.log(value);return Promise.reject('oh, no!');
}).catch(function(e) {console.log(e); // "oh, no!"
}).then(function(){console.log('after a catch the chain is restored');
}, function () {console.log('Not fired due to the catch');
});// 输出为
// 'Success'
// 'oh, no!'
// 'after a catch the chain is restored

捕抛出的错误

// 抛出一个错误,大多数时候将调用catch方法
var p1 = new Promise(function(resolve, reject) {throw 'Uh-oh!';
});p1.catch(function(e) {console.log(e); // "Uh-oh!"
});// 在异步函数中抛出的错误不会被catch捕获到
var p2 = new Promise(function(resolve, reject) {setTimeout(function() {throw 'Uncaught Exception!';}, 1000);
});p2.catch(function(e) {console.log(e); // 不会执行
});// 在resolve()后面抛出的错误会被忽略
var p3 = new Promise(function(resolve, reject) {resolve();throw 'Silenced Exception!';
});p3.catch(function(e) {console.log(e); // 不会执行
});

如果已解决

//创建一个新的 Promise ,且已解决
var p1 = Promise.resolve("calling next");var p2 = p1.catch(function (reason) {//这个方法永远不会调用console.log("catch p1!");console.log(reason);
});p2.then(function (value) {console.log("next promise's onFulfilled"); console.log(value); 
}, function (reason) {console.log("next promise's onRejected");console.log(reason);
});
// 输出
// next promise's onFulfilled
// calling next

4.3、Promise.prototype.allSettled

    返回一个promise,该promise在所有给定的promise已被解析或被拒绝后解析,并且每个对象都描述每个promise的结果。

兼容性并不是很好,polyfill也并未支持

4.4、Promise.prototype.finally

    返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。

var p1 = new Promise(function(resolve, reject){resolve('success')
})
var p2 = new Promise(function(resolve, reject){reject('error')
})p1.finally(function(){console.log('one')
})
p2.finally(function(rs){console.log('two')
})// 输出
// one
// two

    注意:由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。

4.5、Promise.all

    这个方法返回一个新的promise对象,该promise对象在参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个参数里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含参数里所有promise返回值的数组作为成功回调的返回值,顺序跟参数的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把参数里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。

var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {setTimeout(resolve, 100, 'foo');
});Promise.all([promise1, promise2, promise3]).then(function(values) {console.log(values);
});
// 输出
// [3, 42, "foo"]

4.6、Promise.reject

    返回一个带有拒绝原因reason参数的Promise对象。

Promise.reject("error").then(function(reason) {// 未被调用
}, function(reason) {console.log(reason); // "error"
});Promise.reject(new Error("fail")).then(function(result) {// 未被调用
}, function(error) {console.log(error); // stacktrace
});

4.7、Promise.resolve

    返回一个以给定值解析后的Promise 对象。如果该值为promise,返回这个promise;如果这个值是带有”then” 方法,返回的promise会采用它的最终状态;否则返回的promise将以此值完成。

Promise.resolve("Success").then(function(value) {console.log(value); // "Success"
}, function(value) {// 不会被调用
});

Resolve另一个promise

var original = Promise.resolve(33);
var cast = Promise.resolve(original);
cast.then(function(value) {console.log('value: ' + value);
});
console.log('original === cast ? ' + (original === cast));// 输出
//  original === cast ? true
// value: 33

resolve 包含then的对象参数

// resolve
var p1 = Promise.resolve({ then: function(onFulfill, onReject) { onFulfill("fulfilled!"); }
});p1.then(function(v) {console.log(v); // 输出"fulfilled!"}, function(e) {// 不会被调用
});// reject
var p2 = Promise.resolve({ then: function(onFulfill, onReject) { onReject("rejected!"); }
});p2.then(function(v) {// 不会被调用}, function(e) {console.log(e); // 输出"fulfilled!"
});

4.8、Promise.race

    当参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

var p1 = new Promise(function(resolve, reject) {setTimeout(resolve, 500, 'one');
});var p2 = new Promise(function(resolve, reject) {setTimeout(resolve, 100, 'two');
});Promise.race([p1, p2]).then(function(value) {console.log(value);// Both resolve, but p2 is faster
});
// 输出
// two


五、Promise事件

    使用Promise编写异步代码时,使用reject来处理错误。有时,开发者通常会忽略这一点,导致一些错误没有得到处理。例如:

new Promise((resolve, reject) => {reject('error')
}).then(function(){}).then(function(){})

    由于没有使用catch方法捕获错误,当reject时,抛出的错误则没有被处理。

5.1、unhandledrejection

    当 Promise 被拒绝,但没有提供 reject 函数来处理该 rejection 时,会派发此事件。

window.addEventListener('unhandledrejection', event => {console.log(event.reason); // 打印"Hello, Fundebug!"
});Promise.reject('Hello, Fundebug!');

5.2、rejectionhandled

    当一个Promise错误最初未被处理,但是稍后又得到了处理,则会派发此事件:

window.addEventListener('unhandledrejection', event => {console.log(event.reason); // 打印"Hello, Fundebug!"
})window.addEventListener('rejectionhandled', event => {console.log('rejection handled'); // 1秒后打印"rejection handled"
});function foo() {return Promise.reject('Hello, Fundebug!');
}var r = foo();setTimeout(() =>{r.catch(e =>{});
}, 1000);

    我们可以通过以上事件为 Promise 失败时提供补偿处理,也有利于调试 Promise 相关的问题。在每一个上下文中,该处理都是全局的,因此不管源码如何,所有的错误都会在同一个handler中被捕捉处理。

举例如下:

window.addEventListener("unhandledrejection", event => {/* 你可以在这里添加一些代码,以便检查event.promise 中的 promise 和event.reason 中的 rejection 原因 */event.preventDefault();
}, false);


六、高级使用方式

6.1、链式调用

    连续执行两个或者多个异步操作是一个常见的需求,在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。我们可以通过创造一个 Promise 链来实现这种需求。

var promise = new Promise(function(resolve, reject){resolve('promise')
})
promise.then(function(rs){console.log(rs)return 'success'
}).then(function(rs){console.log(rs)return new Promise(function(resolve, reject) {setTimeout(function(){reject('error')}, 3000)})
}).then(function(){// 不会执行console.log('不会执行')
}, function (rs) {console.log(rs)return 'finish'
}).then(function(rs){console.log(rs)
})// 输出
// promise
// success
// error
// finish

    注意:一定要有返回值,否则,callback 将无法获取上一个 Promise 的结果。

6.2、Catch 的后续链式操作

    有可能会在一个回调失败之后继续使用链式操作,即 使用一个 catch,这对于在链式操作中抛出一个失败之后,再次进行新的操作很有用。请阅读下面的例子:

new Promise((resolve, reject) => {console.log('初始化');resolve();
})
.then(() => {throw new Error('有哪里不对了');console.log('执行「这个」”');
})
.catch(() => {console.log('执行「那个」');
})
.then(() => {console.log('执行「这个」,无论前面发生了什么');
});// 输出结果
// 初始化
// 执行“那个”
// 执行“这个”,无论前面发生了什么

    注意:因为抛出了错误 有哪里不对了,所以前一个 执行「这个」 没有被输出。

6.3、执行顺序

    为了避免意外,即使是一个已经变成 resolve 状态的 Promise,传递给 then() 的函数也总是会被异步调用:

Promise.resolve().then(function(){console.log(2)
})
console.log(1); // 1, 2

    传递到 then() 中的函数被置入了一个微任务队列,而不是立即执行,这意味着它是在 JavaScript 事件队列的所有运行时结束了,事件队列被清空之后,才开始执行:

var loading = new Promise(function (resolve, reject) {setTimeout(resolve)
})
loading.then(function () {console.log(4)
});
Promise.resolve().then(function(){console.log(2)
}).then(function(){console.log(3)
});
console.log(1); // 1, 2, 3, 4

微任务宏任务参考文章[http://www.manongjc.com/article/9144.html。

  • promise微任务;

  • setTimeout宏任务。

6.4、嵌套

    then回调函数中如果返回了新的异步对象,那么后续链式调用的then都会等待新的异步对象完成才会继续向下执行,如:

var p1 = new Promise(function(resolve, reject){console.log('p1')resolve()
})
p1.then(function(){return new Promise(function(resolve, reject) {setTimeout(function(){console.log('p2')resolve()}, 1000)}).then(function(){console.log('p3')}).then(function(){console.log('p4')})
}).then(function(){console.log('p5')
})
// 输出
// p1 
// p2
// p3
// p4
// p5

    简便的 Promise 链式编程最好保持扁平化,不要嵌套 Promise,因为嵌套会导致可读性降低,代码也不容易排除,上述逻辑优化如下:

var p1 = new Promise(function(resolve, reject){console.log('p1')resolve()
})
p1.then(function(){return new Promise(function(resolve, reject) {setTimeout(function(){console.log('p2')resolve()}, 1000)})
}).then(function(){console.log('p3')
}).then(function(){console.log('p4')
}).then(function(){console.log('p5')
})
// 输出
// p1 
// p2
// p3
// p4
// p5


七、Promise的缺点

  • 代码有风险造成未解决的promise;

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消;

  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部;

  • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

八、async await

    async/await 是 ES7 引入的新的异步代码 规范,它提供了一种新的编写异步代码的方式,这种方式在语法层面提供了一种形式上非常接近于同步代码的异步非阻塞代码风格,在此之前我们使用的多是异步回调、 Promise 模式。

使用方式

8.1、async 关键字

    async function 用来定义一个返回 AsyncFunction 对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果。

async function name([param[, param[, ... param]]]) { statements }

    name 函数名称。param 要传递给函数的参数。statements 函数体语句。返回值 返回的Promise对象会运行执行(resolve)异步函数的返回结果,或者运行拒绝(reject)——如果异步函数抛出异常的话。

8.2、await 关键字

    await 操作符用于等待一个Promise 对象。它只能在异步函数 async function 中使用。await 会暂停当前 async function 的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled,其回调的resolve函数参数作为 await 表达式的值,继续执行 async function。若 Promise 处理异常(rejected)await 表达式会把 Promise 的异常原因抛出。另外,如果 await 操作符后的表达式的值不是一个 Promise,则返回该值本身。

[return_value] = await expression;
  • 表达式 一个 Promise 对象或者任何要等待的值。

  • 返回值 返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。

示例

async function f1() {var x = await resolveAfter2Seconds(10);console.log(x); // 10
}
f1();

8.3、并联的 await

    async/await 语法确实很简单好用,但在现实场景中也容易出现一些问题。以下面代码为例:

async function retriveProfile(email) {const user = await getUser(email);const roles = await getRoles(user);const level = await getLevel(user);return [user, roles, level];
}

    上面代码实现了获取用户基本信息,然后通过基本信息获取用户角色、级别信息的功能,其中 getRoles 与 getLevel 两者之间并无依赖,是两个并联的异步操作。但代码中 getLevel 却需要等待 getRoles resolve 之后才能执行。并不是所有人都会犯这种错误,而是同步风格很容易诱惑我们忽略掉真正的异步调用次序,而陷入过于简化的同步思维中。写这一段的目的正是为了警醒大家,async 只是形式上的同步,根本上还是异步的,请注意不要让使用者把时间浪费在无谓的等待上。上面的逻辑,用一种稍微 绕 一些的方式来实现,就可以避免这种性能损耗:

async function retriveProfile(email) {const user = await getUser(email);const p1 = getRoles(user);const p2 = getLevel(user);const [roles, levels] = await Promise.all(p1, p2);return [user, roles, levels];
}

    注意,代码中的 getRoles 、getLevel 函数都没有跟在 await 关键字之后,而是把函数返回的 Promise 存放在变量 p1、p2 中,后续才对 p1、p2 执行 await 声明, getRoles 、getLevel 就能同时执行,不需等待另一方的完成。

九、错误处理

使用try…catch

async function asyncCall() {try {await asyncFunc();throw new Error("oops");} catch (e) {console.log(e);// output// Error: oops  at asyncCall (<anonymous>:4:11)}
}

包装promise,使其返回统一的格式的代码

/*** 包装promise, 使其返回统一的错误格式* @param {Promise} promise */
function to (promise) {// 第一个标识异常数据return promise.then(res => [null, res]).catch(err => [err])
}
const [err, res] = await to(fetchUser(true))
if (err) {console.error('touser err:', err)
}

继续使用catch

// 因为async 返回的promise对象,所以可以使用catchconst user4 = await fetchUser(true).catch(err => {console.error('user4 error:', err)})

优点

  • async/await从上到下,顺序执行,就像写同步代码一样。这更符合人编写代码的习惯。

缺点

  • 编译后增加了代码的体积;

  • 编译后的代码不容易理解,会给调试时带来一定困扰。


十、jQuery 异步对象

    jquery中试通过$.Deferred来实例化异步对象的,deferred对象就是jQuery的回调函数解决方案。在英语中,defer的意思是”延迟”,所以deferred对象的含义就是”延迟”到未来。

简单示例

在 jQuery 中

var deferred = $.Deferred();
var promise = deferred.promise();

在 ES6 中

var promise= function (function(resolve, reject){
})

处理状态

    一个 deferred对象 能做的和一个promise对象差不多,它也有resolve和reject两个函数来触发 done()和fail()函数 。

在jQuery中

var deferred = $.Deferred();
setTimeout(function(){deferred.resolve('success')
}, 2000)
var promise = deferred.promise();

在 ES6 中

var promise = new Promise(function(resolve, reject){resolve('success')
})

    jquery的延时对象可以在外面解决,二promise的异步对象在实例化的回调中解决,相比promise的更加安全,语法也简洁。

等待多个异步完成

在jQuery中

var deferred1 = $.Deferred();
deferred1.resolve('promise1');
var promise1 = deferred1.promise();
var deferred2 = $.Deferred();
deferred2.resolve('promise2');
var promise2 = deferred2.promise();
$.when(promise1, promise2).then(function(rs1, rs2){console.log(rs1, rs2)// promise1, promise2
})

在 ES6 中

var promise1 = Promise.resolve('promise1')
var promise2 = Promise.resolve('promise2')
Promise.all([promise1, promise2]).then(function(rs){console.log(rs) // ['promise1', 'promise2']
})

成功或失败都触发

在jQuery中

var deferred1 = $.Deferred();
deferred1.reject('promise1');
var promise1 = deferred1.promise();
promise1.always(function(rs){console.log(rs) // promise1
})
var deferred2 = $.Deferred();
deferred2.resolve('promise2');
var promise2 = deferred2.promise();
promise2.always(function(rs){console.log(rs) // promise2
})

在 ES6 中

var promise1 = Promise.reject('promise1')
var promise2 = Promise.resolve('promise2')
promise1.finally(function(rs){console.log(rs) // undefined
})
promise2.finally(function(rs){console.log(rs) // undefined
})

ES6中Promise的finally回调中并不会有解决的结果。

十一、Promise常见应用场景

按钮锁定,防止重复提交

    实际场景中往往存在用户提交后,需要等待后台逻辑完毕后,才能执行其它的操作,那么这个过程中需要锁定界面相关操作按钮的,防止用户做其他不符合业务的操作,如还没等表单保存完毕就发起审批,那么这种情况代码中应该怎么处理,下面从代码层面简单说明一下:

// main.js
module.exports = {// 是否保存中saving: false,// 保存按钮点击onSaveClick: function () {var me = this// 先判断如果处于保存中,不允许再次提交保存if (this.saving) {return}this.save().then(function(){// 释放saving状态,可继续执行保存操作me.saving = false}, function(){// 失败时saving也要处理为falseme.saving = false})},// 审批按钮点击onApplyClick: function () {// 如果处于保存中,不允许执行审批操作if (this.saving) {return}...},// 保存save: function () {return new Promise(function(resolve, reject){// 向后端发起请求Ajax({...        }).then(resolve, reject)})}
}

    这里主要是借助了save方法返回了异步Promise对象来完成对saving状态的释放,实际的场景会比这个更复杂,比如校验或者按钮的loading效果,但是这仅是逻辑与交互的叠加,主要的处理点还是这里。

实现串行任务队列

    有些表单提交前是需要做校验的,比如新增签约用户,需要校验客户的身份证号以及手机号的正确性后才能保存,并且保存完毕后需要弹出提示,这就需要借助Promise的请求依赖完成这个需求场景,相关代码如下:

// form.js
module.exports = {    // 校验客户名称是否重名validateName: function () {return new Promise(function(resolve, reject) {...resolve()})},// 校验手机号是否存在validateMobile: function () {// Ajax仅表示通过jQuery或者平台ajax返回的异步对象return Ajax({...})},// 校验身份证号正确性validateIdNumber: function () {// 如果要使用串行调用方式,即使是前端校验也要返回Promise对象,如return new Promise(function(resolve, reject) {// 伪代码,实际应用时不做参考if (/^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(this.formData.idNumber)) {resolve()} else {reject()}})},// 保存表单saveForm: function () {var me = thisreturn me.validateName().then(function(){return me.validateMobile()}).then(function(){return me.validateIdNumber()}).then(function(){return Ajax({...})}).then(function(){alert('保存成功')}, function () {alert('保存失败')})},// 用户点击保存按钮onSaveClick: function () {return this.saveForm()}
}

    这个场景中,如果用户的名称校验失败了,那么不会走到后面的校验手机号,直接会到最后的保存失败,这也是符合实际业务的,只有前面的校验全部通过了才会到最后的保存,整个环节是串行的任务执行。

多个异步任务并发控制

    在页面初始化的时候,往往会发送很多的请求从后端获取数据,在这个阶段页面会显示一个loading遮罩层,等所有的请求完毕后再将这个遮罩层关闭掉,这个时候需要借助到Promise并发多个异步,同时监听异步完成后的动作,大致的代码如下:

// main.js
module.exports = {// 页面初始化pageInit: function () {// 页面开始loadingthis.pageLoading = true// 加载用户数据、证件类型数据、购房信息数据,Promise.all会监听异步队列完毕后执行then中的回调,具体细节可参考上诉Promise.all的用法Promise.all([this.getUserData(),this.getCertificateData(),this.getBuyRoomInfo()]).then(function(){// 页面关掉loadingthis.pageLoading = false}, function () {// 页面关掉loadingthis.pageLoading = false})},// 获取用户数据getUserData: function () {return new Promise(function(resolve, reject) {...resolve({userName: 'xxx',...})})},// 获取证件类型数据getCertificateData: function () {// Ajax仅表示通过jQuery或者平台ajax返回的异步对象return Ajax({...})},// 获取用户购买房间信息getBuyRoomInfo: function () {return new Promise(function(resolve, reject) {...resolve({roomName: '房间名称',...})})}
}

小结

    Promise是一种很好的编程设计思想,在设计一些对外的耗时异步API时候,也可以借助Promise,而非传统的callback方式,可以很优雅的对外输出。同时这又需要团队内的成员都需要熟悉Promise的使用,才能更好的发挥它的威力。

------ END ------

作者简介

杨同学: 研发工程师,目前负责ERP建模平台的设计与开发工作。

也许您还想看

从案例角度解析建模平台动态规则引擎

WEB页面前端性能诊断方法与实践

【复杂系统迁移 .NET Core平台系列】之界面层

链路追踪在ERP系统中的应用实践


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310016.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java1.8的stream_JDK1.8新特性(一):stream

搜索热词一.什么是stream&#xff1f;1.概述Java 8 API添加了一个新的抽象称为流Stream&#xff0c;可以让你以一种声明的方式处理数据。这种风格将要处理的元素集合看作一种流&#xff0c; 流在管道中传输&#xff0c; 并且可以在管道的节点上进行处理&#xff0c; 比如筛选&a…

[C++STL]常用集合算法

代码如下: #include <iostream> #include <vector> #include <numeric> #include <algorithm> using namespace std;class myPrint { public:void operator()(int val){cout << val << " ";} };void test01() {vector<int&g…

Seek the Name, Seek the Fame POJ - 2752 (理解KMP函数的失配)既是S的前缀又是S的后缀的子串

题意&#xff1a;给一个字符串S&#xff0c; 求出所有前缀pre&#xff0c;使得这个前缀也正好是S的后缀。 输出所有前缀的结束位置。 就是求前缀和后缀相同的那个子串的长度 然后从小到大输出,主要利用next数组求解。 例如 “ababcababababcabab”&#xff0c; 以下这些前缀…

2020 年了,WPF 还有前途吗?

2020年了&#xff0c;微软的技术也不断更新了, .Net Core 3.1、.Net Framework 4.8以及今年11月推出的.NET 5...win10平台也普及了很多&#xff0c;WPF可以在上面大展身手&#xff0c;可性能和内存占用还是不行&#xff0c;但是WPF强大的UI能力很吸引人。WPF已经凉了吗? 学WPF…

[C++STL]常用查找算法

代码如下: #include <iostream> #include <algorithm> #include <vector> #include <string> using namespace std;void test01() {vector<int>v;for (int i 0; i < 10; i){v.push_back(i);}vector<int>::iterator it find(v.begin(…

php asp 语法,ASP 语法

在我们的 ASP 教程中&#xff0c;每个实例都提供隐藏的 ASP 源代码。这样会使您更容易理解它们的工作原理。向浏览器写输出ASP 文件通常包含 HTML 标签&#xff0c;就像 HTML 文件。然而&#xff0c;ASP 文件也能包含服务器脚本&#xff0c;这些脚本被分隔符 包围起来。服务器脚…

#10003. 「一本通 1.1 例 4」加工生产调度(贪心)

加工生产调度 题目描述 某工厂收到了n个产品的订单&#xff0c;这n个产品分别在A、B两个车间加工&#xff0c;并且必须先在A车间加工后才可以到B车间加工。 某个产品i在A、B两车间加工的时间分别为Ai、Bi。询问怎样安排这n个产品的加工顺序&#xff0c;才能使总的加工时间最短…

不要把异常当做业务逻辑,这性能可能你无法承受

一&#xff1a;背景1. 讲故事在项目中摸爬滚打几年&#xff0c;应该或多或少的见过有人把异常当做业务逻辑处理的情况(┬&#xff3f;┬)&#xff0c;比如说判断一个数字是否为整数,就想当然的用try catch包起来&#xff0c;再进行 int.Parse&#xff0c;如果抛异常就说明不是整…

[设计模式]开闭原则

开闭原则: 对扩展开放&#xff0c;对修改关闭。 增加功能是提过增加代码来实现的&#xff0c;而不是去修改源代码。 代码如下: #include <iostream> #include <string> using namespace std;class Caculaor { public:Caculaor(int a,int b,string c):a(a),b(b…

#10010 「一本通 1.1 练习 6」糖果传递 (数学+贪心)

题目描述 原题来自&#xff1a;HAOI 2008 有 n个小朋友坐成一圈&#xff0c;每人有 ai 颗糖果。每人只能给左右两人传递糖果。每人每次传递一颗糖果的代价为 1 。求使所有人获得均等糖果的最小代价。 输入格式 第一行有一个整数 &#xff0c;n表示小朋友个数&#xff1b; …

ASP.NET Core在Docker下面生成简易验证码

背景 验证码这个功能是十分常见的&#xff0c;各大系统的登录页面都会有。今天介绍一下最为普通的验证码。无论最终给到前端的是图片格式的验证码还是base64格式的验证码&#xff0c;其实都离不开这样的一步操作&#xff0c;都要先在后台生成一个图片。就个人经验来说&#xff…

[设计模式]迪米特法则

迪米特法则 又叫最少知识法则 类中的成员属性和成员方法&#xff0c;如果不需要对外暴露&#xff0c;就不要设成public。 代码如下: #include <iostream> #include <string> using namespace std;class AbstractBuilding { public:virtual void sale() 0; };cl…

#10017 「一本通 1.2 练习 4」传送带+三分套三分

题目描述 原题来自&#xff1a;SCOI 2010 在一个 2 维平面上有两条传送带&#xff0c;每一条传送带可以看成是一条线段。两条传送带分别为线段 AB和线段CD 。lxhgww 在 AB上的移动速度为 P &#xff0c;在 CD上的移动速度为 Q &#xff0c;在平面上的移动速度R 。 现在 l…

程序员过关斩将--从用户输入手机验证码开始

点击“蓝字”关注我们吧菜菜哥&#xff0c;请教个问题呗&#xff1f;说说看&#xff0c;能否解决不敢保证哦最近做的App业务中&#xff0c;有很多敏感操作需要用户输入手机验证码这没问题&#xff0c;手机验证码主要是为了验证当前操作人的有效性&#xff0c;有什么问题呢&…

Ticket Game CodeForces - 1215D(博弈题,巴什博弈思维)

题意&#xff1a;两个人玩游戏&#xff0c;通过轮流填数字&#xff08;0~9&#xff09;&#xff0c;若最终左右两边的和相等&#xff0c;后手赢&#xff0c;否则先手赢。起始有部分数字和空格。 官方题解&#xff1a; 题解翻译&#xff1a; 让我们把余额表示为左半部分数字和…

[设计模式]合成复用原则

合成复用原则:继承和组合&#xff0c;优先使用组合。 这样写&#xff0c;每开一种车&#xff0c;就要弄一个新的Person类。 代码如下: #include <iostream> using namespace std;class AbstractCar { public:virtual void run() 0; };class DaZhong :public AbstractC…

帮 vs2019 找回丢失的 SDK

缘起 前一段时间&#xff0c;有网友遇到一个奇怪的问题&#xff0c;说他机器上的 vs2019 编译 C 工程报错。我当时一听就有两个怀疑&#xff1a;工程设置不对。vs2019 没装好。因为新建一个最简单的工程&#xff0c;编译也报一样的错误&#xff0c;所以可以排除工程设置的问题了…

java keysetview,Set——你真的了解吗?

JAVA 基础 &#xff1a;Set——你真的了解吗&#xff1f;简述Set 继承于 Collection &#xff0c;是一种集合。有元素无序、值不重复、不允许空值得特性。主要有HashSet、TreeSet两种实现方式。由于Set主要基于Map实现&#xff0c;所以特点也由Map决定。Set 结构图例如 HashSet…

Minimizing Difference CodeForces - 1244E(贪心题)

题目题意官方题解&#xff1a;百度翻译思路ac代码题意 给出一列数&#xff0c;至多n个操作使其中的数1或-1&#xff0c;要求得到最小的差值&#xff08;最大值-最小值&#xff09;&#xff1b; You are given a sequence a1_{1}1​,a2_{2}2​,…,an_{n}n​ consisting of nn …

[设计模式]依赖倒转原则

代码如下: #include <iostream> #include <string>using namespace std;//银行工作人员 class BankWorker { public:void saveService(){cout << "办理存款业务" << endl;}void payService(){cout << "办理支付业务" <&…