关于promise:一篇搞定-Promise

一、Promise是什么在 JavaScript 中,Promise 是一种解决异步操作的形式。Promise 能够让咱们更加优雅地解决异步操作,而不须要应用回调函数嵌套。 ● Promise 是一门新的技术(ES6 标准),是 JS 中进行异步编程的新解决方案(旧计划是单纯应用回调函数,容易造成回调天堂) ● 常见异步操作:①fs 文件操作 ②数据库操作 ③Ajax ④定时器 ● 具体表白: 从语法上看:Promise是一个构造函数 (本人身上有all、reject、resolve这几个办法,原型上有then、catch等办法)从性能上看:promise对象用来封装一个异步操作并能够获取其胜利/失败的后果值二、创立 Promise创立一个 Promise 非常简单,只须要应用 Promise 构造函数即可。Promise 构造函数承受一个函数作为参数,这个函数叫做执行器函数(executor function)。执行器函数有两个参数:resolve 和 reject。当异步操作胜利时,调用 resolve,并传递后果值作为参数;当异步操作失败时,调用 reject,并传递谬误对象作为参数。 const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作胜利 */){ resolve(value); } else { reject(reason); }});三、应用 Promise应用 Promise 有两种形式:then 办法和 catch 办法。 1.then 办法then 办法用于注册 Promise 胜利时的回调函数。当 Promise 胜利时,then 办法会被调用,并传递 Promise 胜利时的后果值作为参数。 ...

March 8, 2023 · 5 min · jiezi

关于promise:使用Promiserace实现控制并发

之前做过一个 Node.js实现分片上传 的性能。过后前端采纳文件切片后并发上传,大大提高了上传的速度,应用Promise.race()治理并发池,无效防止浏览器内存耗尽。当初的问题:Node.js服务端合并大文件分片 内存耗尽导致服务重启 服务端代码const ws = fs.createWriteStream(`${target}/${filename}`); // 创立将要写入文件分片的流const bufferList = fs.readdirSync(`${STATIC_TEMPORARY}/${filename}`); // 读取到分片文件名的列表[1,2,3...] 每个分片1M大小// 2. 不会阻塞EventLoopbufferList.forEach((hash, index) => { fs.readFile(`${STATIC_TEMPORARY}/${filename}/${index}`, (err, data) => { ws.write(data); });});服务器配置:RAM:1024MB 1vCPU (运行了一些其余服务) 测试发现只有上传的文件超过300M,在合并分片时就会导致内存耗尽服务重启,根本无法实现分片合并。 解决方案:能不能在循环中管制读取文件的并发数呢?这样就不会有大量文件同时读取到内存中导致服务解体了。 尝试像前端那样应用 Promise.race 管制: const ws = fs.createWriteStream(`${target}/${filename}`); // 创立将要写入文件分片的流const bufferList = fs.readdirSync(`${STATIC_TEMPORARY}/${filename}`); // 读取到分片文件名的列表[1,2,3...] 每个分片1M大小// Promise.race 并发管制const pool = [];let finish = 0; // 曾经写入实现的分片数量// 应用Promise包裹读取文件的异步操作const PR = (index) => { return new Promise((resolve) => { fs.readFile(`${STATIC_TEMPORARY}/${filename}/${index}`, (err, data) => { ws.write(data) resolve({}); }); });};(async function easyRead() { for (let i = 0; i < bufferList.length; i ++) { const task = PR(i).then(val => { finish+=1 const index = pool.findIndex(t => t === task); pool.splice(index); if (finish === bufferList.length) { ws.close(); } }); pool.push(task); if (pool.length === 1) { // 这里并发数量只能是1 否则分片写入是乱序的 格局会被损坏 await Promise.race(pool); } }})()这时神奇的事件就产生了,咱们在for循环中应用了Promise.race()管制了同一时间读入到内存中的文件数量。 ...

September 6, 2022 · 1 min · jiezi

关于promise:取消一个promise

场景:发送多个申请后,当回来的数据要渲染到页面时,DOM元素被销毁了,此时如果要渲染就会报错比方 react 在 useEffect 中申请的数据时,组件销毁 useEffect(() => { let dataPromise = new Promise(...); let data = dataPromise().then(); // TODO 接下来解决 data,此时本组件可能曾经销毁了,dom 也不存在了,所以须要在上面对 Promise 进行中断 return (() => { // TODO 组件销毁时,对 dataPromise 进行中断或勾销 }) });能够对生成的 Promise 对象进行再一次包装,返回一个新的 Promise 对象,而新的对象上被咱们减少了 cancel 办法,用于勾销。这里的原理就是在 cancel 办法外面去阻止 Promise 对象执行 then()办法。 function getPromiseWithCancel(originPromise) { let cancel = (v) => {}; let isCancel = false; const cancelPromise = new Promise(function (resolve, reject) { cancel = e => { isCancel = true; reject(e); }; }); const groupPromise = Promise.race([originPromise, cancelPromise]) .catch(e => { if (isCancel) { // 被动勾销时,不触发外层的 catch return new Promise(() => {}); } else { return Promise.reject(e); } }); return Object.assign(groupPromise, { cancel });}// 应用如下const originPromise = axios.get(url);const promiseWithCancel = getPromiseWithCancel(originPromise);promiseWithCancel.then((data) => { console.log('渲染数据', data);});promiseWithCancel.cancel(); // 勾销 Promise,将不会再进入 then() 渲染数据

August 22, 2022 · 1 min · jiezi

关于promise:遵循PromisesA规范深入分析Promise实现细节-通过872测试样例

前言本周写文的外围为 Promise ,Promise 大家应该都特地相熟了,Promise 是异步编程的一种解决方案,宽泛用在日常编程中。本周小包将围绕 Promise 源码手写进行写文,源码手写初步打算应用三篇文章实现—— 手写 Promise 之根底篇,手写 Promise 之 resolvePromise 篇,手写 Promise 之静态方法篇。 Promises/A+ 标准是 Promise 的实现准则,因而 Promise 手写系列将遵循 Promises/A+ 标准的思路,以案例和发问形式层层深刻,一步一步实现 Promise 封装。 学习本文,你能播种: 了解 Promise A+标准 了解什么是 Promise 的值穿透、Promise 链式调用机制、Promise 注册多个 then 办法等。 把握 Promise 源码编写全过程 把握公布订阅模式在 Promise 源码编写中的应用根底铺垫Promise 必然处于下列三种状态之一: Pending 期待态: 初始状态,不是胜利或失败状态。Fulfilled 实现态: 意味着操作胜利实现。Rejected 失败态: 意味着操作成功失败。当 promise 处于 Pending 状态时,能够转变为 Fulfilled 或者 Rejected当 promise 处于 Fulfilled 或 Rejected 时,状态不能再产生扭转 那什么会触发 promise 中状态的扭转呐?咱们来看几个栗子: // p1 什么都不执行且传入空函数const p1 = new Promise(() => {});console.log("p1: ", p1);// p2 执行 resolveconst p2 = new Promise((resolve, reject) => { resolve("success");});console.log("p2: ", p2);// p3 执行 rejectconst p3 = new Promise((resolve, reject) => { reject("fail");});console.log("p3: ", p3);// p4 抛出谬误const p4 = new Promise((resolve, reject) => { throw Error("error");});console.log("p4: ", p4);// p5 先执行 resolve 后执行 rejectconst p5 = new Promise((resolve, reject) => { resolve("success"); reject("fail");});console.log("p5: ", p5);// p6 什么都不执行且不传参const p6 = new Promise();console.log("p6: ", p6);复制代码咱们来看一下输入后果: ...

March 3, 2022 · 10 min · jiezi

关于promise:Promise理解看了又看

Promise 哪些 API 波及了微工作?Promise 中只有波及到状态变更后才须要被执行的回调才算是微工作,比如说 then、 catch 、finally ,其余所有的代码执行都是宏工作(同步执行)。上图中蓝色为同步执行,黄色为异步执行(丢到微工作队列中)。 这些微工作何时被退出微工作队列?这个问题咱们依据 ecma 标准来看: 如果此时 Promise 状态为 pending,那么胜利或失败的回调会别离被退出至 [[PromiseFulfillReactions]] 和 [[PromiseRejectReactions]] 中。如果你看过手写 Promise 的代码的话,应该能发现有两个数组存储这些回调函数。如果此时 Promise 状态为非 pending 时,回调会成为 Promise Jobs,也就是微工作。同一个 then,不同的微工作执行高级Promise.resolve() .then(() => { console.log("then1"); Promise.resolve().then(() => { console.log("then1-1"); }); }) .then(() => { console.log("then2"); }); 尽管 then 是同步执行,并且状态也曾经变更。但这并不代表每次遇到 then 时咱们都须要把它的回调丢入微工作队列中,而是期待 then 的回调执行结束后再依据状况执行对应操作。 基于此,咱们能够得出第一个论断:链式调用中,只有前一个 then 的回调执行结束后,跟着的 then 中的回调才会被退出至微工作队列。 中级大家都晓得了 Promise resolve 后,跟着的 then 中的回调会马上进入微工作队列。 那么以下代码你认为的输入会是什么? let p = Promise.resolve();p.then(() => { console.log("then1"); Promise.resolve().then(() => { console.log("then1-1"); });}).then(() => { console.log("then1-2");});p.then(() => { console.log("then2");}); 依照一开始的认知咱们不难得出 then2 会在 then1-1 后输入,然而理论状况却是相同的。 ...

February 21, 2022 · 2 min · jiezi

关于promise:Promise

Promise理解// Promise承受的参数是函数模式的// resolve(解决) 函数类型的参数 // reject(回绝) 函数类型的参数const p = new Promise((resolve,reject) => { let num = Math.round((Math.random() * 100)) if(num > 50) { resolve(num) // 能够将Promise对象(下面的 p)的状态设置为胜利 }else { reject(num) // 能够将Promise对象(下面的 p)的状态设置为失败 }})p.then((num) => { console.log('胜利了',num) // 如何中断Peomise // return new Promise(() => {)},(num) => { console.log('失败了',num)})// console.log(p) 会有一个Promise实例,下面有两个属性PromiseState1. pending 未决定的2. rejected 失败3. resolve/fulfilled 胜利PromiseResult保留着对象胜利或者失败的后果只有以下两个能够操作这个后果1. resolve2. reject手写Promise代码function Promise(excutor) { // 增加两个默认属性 this.PromiseState = 'pending' this.PromiseResult = null // 定义一个回调应用 this.callbacks = [] // 函数外面的this指向window,所以保留下 const that = this; // resolve函数 function resolve(data) { // PromiseState只能批改一次,不是pending状态间接retrun if(that.PromiseState !== 'pending') return // 1.批改对象的状态(PromiseState) that.PromiseState = 'fulfilled' // 2.批改对象后果值(PromiseResult) that.PromiseResult = data // 调用胜利函数 // 这个定时器为了体现异步 setTimeout(() => { that.callbacks.forEach(element => { element.onResolved(data) }); }) } // reject函数 function reject(data) { // PromiseState只能批改一次,不是pending状态间接retrun if(that.PromiseState !== 'pending') return // 1.批改对象的状态(PromiseState) that.PromiseState = 'rejected' // 2.批改对象后果值(PromiseResult) that.PromiseResult = data // 调用胜利函数 // 这个定时器为了体现异步 setTimeout(() => { that.callbacks.forEach(element => { element.onRejected(data) }); }) } // 兼容 throw 抛出异样 try { // 同步调用 《执行器函数》 excutor(resolve,reject) }catch(err) { reject(err) }}// 增加then办法Promise.prototype.then = function (onResolved,onRejected) { const that = this if(typeof onRejected !== 'function') { onRejected = error => { throw error } } if(typeof onResolved !== 'function') { onResolved = value => value } return new Promise((resolve,reject) => { // 封装函数 function callback(type) { try{ // 获取回调函数的执行后果 let result = type(that.PromiseResult) // 判断以后数据是不是一个Promise if(result instanceof Promise) { // 如果是,那必定能够调用then办法 result.then((val) => { resolve(val) },(err) => { reject(err) }) }else { // 如果不是,间接返回后果即可 resolve(result) } }catch(err){ reject(err) } } if(this.PromiseState === 'fulfilled') { // 这个定时器为了体现异步 setTimeout(() => { callback(onResolved) }) } if(this.PromiseState === 'rejected') { // 这个定时器为了体现异步 setTimeout(() => { callback(onRejected) }) } if(this.PromiseState === 'pending') { this.callbacks.push({ onResolved: function() { callback(onResolved) }, onRejected: function() { callback(onRejected) } }) } })}Promise.prototype.catch = function(onRejected) { return this.then(undefined,onRejected)}Promise.resolve = function(value) { return new Promise((resolve,reject) => { if(value instanceof Promise) { // 如果是,那必定能够调用then办法 value.then((val) => { resolve(val) },(err) => { reject(err) }) }else { // 如果不是,间接返回后果即可 resolve(value) } })}Promise.reject = function(error) { return new Promise((resolve,reject) => { reject(error) })}Promise.all = function(promises) { return new Promise((resolve,reject) => { // 申明一个变量来判断数组中是不是都执行胜利了 let count = 0 let arr = [] for(let i = 0;i < promises.length;i++) { promises[i].then(val => { count++; arr[i] = val // 当所有都执行成了,就返回胜利 if(count === promises.length){ resolve(arr) } },err => { reject(err) }) } })}Promise.race = function(promises) { return new Promise((resolve,reject) => { // 申明一个变量来判断数组中是不是都执行胜利了 for(let i = 0;i < promises.length;i++) { promises[i].then(val => { resolve(val) },err => { reject(err) }) } })}手写Promise代码-Class版本class Promise { // 构造方法 constructor(excutor) { // 增加两个默认属性 this.PromiseState = 'pending' this.PromiseResult = null // 定义一个回调应用 this.callbacks = [] // 函数外面的this指向window,所以保留下 const that = this; // resolve函数 function resolve(data) { // PromiseState只能批改一次,不是pending状态间接retrun if(that.PromiseState !== 'pending') return // 1.批改对象的状态(PromiseState) that.PromiseState = 'fulfilled' // 2.批改对象后果值(PromiseResult) that.PromiseResult = data // 调用胜利函数 // 这个定时器为了体现异步 setTimeout(() => { that.callbacks.forEach(element => { element.onResolved(data) }); }) } // reject函数 function reject(data) { // PromiseState只能批改一次,不是pending状态间接retrun if(that.PromiseState !== 'pending') return // 1.批改对象的状态(PromiseState) that.PromiseState = 'rejected' // 2.批改对象后果值(PromiseResult) that.PromiseResult = data // 调用胜利函数 // 这个定时器为了体现异步 setTimeout(() => { that.callbacks.forEach(element => { element.onRejected(data) }); }) } // 兼容 throw 抛出异样 try { // 同步调用 《执行器函数》 excutor(resolve,reject) }catch(err) { reject(err) } } then(onResolved,onRejected) { const that = this if(typeof onRejected !== 'function') { onRejected = error => { throw error } } if(typeof onResolved !== 'function') { onResolved = value => value } return new Promise((resolve,reject) => { // 封装函数 function callback(type) { try{ // 获取回调函数的执行后果 let result = type(that.PromiseResult) // 判断以后数据是不是一个Promise if(result instanceof Promise) { // 如果是,那必定能够调用then办法 result.then((val) => { resolve(val) },(err) => { reject(err) }) }else { // 如果不是,间接返回后果即可 resolve(result) } }catch(err){ reject(err) } } if(this.PromiseState === 'fulfilled') { // 这个定时器为了体现异步 setTimeout(() => { callback(onResolved) }) } if(this.PromiseState === 'rejected') { // 这个定时器为了体现异步 setTimeout(() => { callback(onRejected) }) } if(this.PromiseState === 'pending') { this.callbacks.push({ onResolved: function() { callback(onResolved) }, onRejected: function() { callback(onRejected) } }) } }) } catch(onResolved,onRejected) { return this.then(undefined,onRejected) } static resolve = function(value) { return new Promise((resolve,reject) => { if(value instanceof Promise) { // 如果是,那必定能够调用then办法 value.then((val) => { resolve(val) },(err) => { reject(err) }) }else { // 如果不是,间接返回后果即可 resolve(value) } }) } static reject (error) { return new Promise((resolve,reject) => { reject(error) }) } static all (promises) { return new Promise((resolve,reject) => { // 申明一个变量来判断数组中是不是都执行胜利了 let count = 0 let arr = [] for(let i = 0;i < promises.length;i++) { promises[i].then(val => { count++; arr[i] = val // 当所有都执行成了,就返回胜利 if(count === promises.length){ resolve(arr) } },err => { reject(err) }) } }) } static race (promises) { return new Promise((resolve,reject) => { // 申明一个变量来判断数组中是不是都执行胜利了 for(let i = 0;i < promises.length;i++) { promises[i].then(val => { resolve(val) },err => { reject(err) }) } }) }}

January 28, 2022 · 4 min · jiezi

关于promise:PromiseA规范

promiseA+ 标准常见术语promise,一种约定,是一个有then办法的对象或者函数,行为遵循本标准thenable,一个有then办法的对象或者函数value,promise胜利后的值,resolve的参数,类型:number、boolean、undefined、promisereason,promise失败后的值。reject的参数,示意回绝的起因exception,异样值标准Promise States有三种状态,pending、fulfilled、rejected pending 1.1 初始状态,可扭转 1.2 一个Promise在 resolve/reject 之前都处于这个状态 1.3 resolve: pending => fulfilled 状态 1.4 reject: pending => rejected 状态 fulfilled 2.1 最终状态,不可扭转 2.2 一个Promise在被resolve之后会变成这个状态 2.3 必须领有一个value值 rejected 3.1 最终状态,不可扭转 3.2 一个Promise在被reject之后会变成这个状态 3.3 必须领有一个reason值 pending -> resolve(value) -> fulfilled pending -> reject(reason) -> rejectedthenpromise 应该提供一个then办法,用来拜访最终的后果 promise.then(onFulfilled, onRejected)参数要求 1.1 onFulfilled 必须是一个函数类型,如果不是函数,应该被疏忽 (给一个默认函数) 1.2 onRejected 必须是一个函数类型,如果不是函数,应该被疏忽 (给一个默认函数) onFulfilled 个性 2.1 在 promise 变成 fulfilled 时,应该调用 onFulfilled,参数是 value 2.2 在 promise 变成 fulfilled 之前,不应该被调用 2.3 只能被调用一次 ...

January 27, 2022 · 2 min · jiezi

关于promise:爪哇学习笔记根据PromiseA规范来实现一个promise

1. 术语promise是一个有then办法的对象或函数,它的行为遵循本标准thenable是一个定义了then办法的对象或函数value是任意一个非法的JavaScript值,是promise状态为胜利时的值exception是一个应用throw关键字抛出的异样值reason是promise状态为失败时的值,示意promise失败的起因2. 标准2.1 Promise States一个promise必须处于三种状态之一:pending, fulfilled, rejected. pending 初始状态,可扭转(改变方式只有以下两种)可由pending变为fulfilled可由pending变为rejectedfulfilled 最终状态,不可变必须领有一个value,且不可变(===不可变)rejected 最终状态,不可变必须领有一个reason,且不可变(===不可变)2.2 then办法一个promise必须提供then办法来拜访其以后或最终value或reason 一个promise的then办法接管两个参数: promise.then(onFulfilled, onRejected)onFulfilled和onRejected都是可选参数 如果onFulfilled不是函数,则必须疏忽它如果onRejected不是函数,则必须疏忽它如果onFulfilled是函数 必须在promise变成fulfilled后调用onFulfilled, 并把value作为第一个参数在promise变成fulfilled之前, 不应该被调用只能被调用一次(所以在实现的时候须要一个变量来限度执行次数)如果onRejected是函数 必须在promise变成rejected后调用onRejected, 并把reason作为第一个参数在promise变成rejected之前, 不应该被调用只能被调用一次(所以在实现的时候须要一个变量来限度执行次数)在执行上下文堆栈仅蕴含平台代码之前,不得调用 onFulfilled 或 onRejected。(即应该应用工作的形式来执行这两个回调函数,本次实现应用微工作形式)onFulfilled 和 onRejected 必须作为函数调用then办法在同一个promise上能够屡次调用 当promise的状态变为fulfilled后,所有的 onFulfilled 回调都须要依照then的程序执行, 也就是依照注册程序执行(所以在实现的时候须要一个数组来寄存多个onFulfilled的回调)当promise状态变成 rejected 后,所有的 onRejected 回调都须要依照then的程序执行, 也就是依照注册程序执行(所以在实现的时候须要一个数组来寄存多个onRejected的回调)then办法必须返回一个promise 如果 onFulfilled 或 onRejected 返回值 x,则须要调用Promise Resolution Procedure: [[Resolve]](promise2, x)如果 onFulfilled 或者 onRejected 执行时抛出异样e,promise2必须被reject,且把reason作为参数如果 onFulfilled 不是一个函数, promise2 以promise1的value 触发fulfilled如果 onRejected 不是一个函数, promise2 以promise1的reason 触发rejected2.3 The Promise Resolution Procedure[[Resolve]](promise, x)如果 promise2 和 x 相等,那么 reject TypeError如果 x 是一个 promsie ...

January 17, 2022 · 4 min · jiezi

关于promise:Promise总结

Promise指标Promise A+标准手写Promise题目练习Promise A+标准术语promise: 一个领有合乎这个标准的行为的then办法的对象或函数。thenable: 定义了一个then办法的对象或函数。value: 任意非法的JavaScript值(包含undefined,thenable,promise)。exception: 应用throw语句抛出的一个值reason: 示意promise为什么被回绝的一个值必要条件Promise 状态promise必须是这三个状态中的一种:期待态pending,解决态fulfilled或回绝态rejected 当promise处于pending状态的时候: 可能变为fulfilled或者rejected状态。当promise处于fulfilled状态的时候: 2.1 肯定不能转换为任何其它状态 2.2 必须有一个不能扭转的value当promise处于rejected状态的时候: 3.1 肯定不能转换为任何其它状态 3.2 必须有一个不能扭转的reason在这里,"肯定不能扭转"意味着不变的身份(例如 ===),然而并不意味着深度不可变性。(译注者:这里应该是说只有值的援用雷同即可,并不需要援用中的每一个值都相等) then办法Promise必须提供一个then办法来拜访以后或最终的value或reason。 Promise的then办法承受俩个参数: promise.then(onFulfilled, onRejected)onFulfilled和onRejected都是可选的参数 1.1. 如果onFulfilled不是一个函数,它必须被疏忽 1.2. 如果onRejected不是一个函数,它必须被疏忽如果onFulfilled是一个函数 2.1. 在promise变成 fulfilled 时,应该调用 onFulfilled, 参数是value 2.2. 在promise变成 fulfilled 之前, 不应该被调用. 2.3. 它肯定不能被调用屡次。如果onRejected是一个函数 2.1. 在promise变成 rejected 时,应该调用 onRejected, 参数是reason 2.2. 在promise变成 rejected 之前, 不应该被调用. 2.3. 它肯定不能被调用屡次。在执行上下文栈中只蕴含平台代码之前,onFulfilled或onRejected肯定不能被调用同一个promise上的then可能被调用屡次 6.1. 如果promise被解决,所有相应的onFulfilled回调必须依照他们原始调用then的程序执行 6.2. 如果promise被回绝,所有相应的onRejected回调必须依照他们原始调用then的程序执行then必须返回一个promise对象promise2 = promise1.then(onFulfilled,onRejected)resolvePromiseresolvePromise(promise2, x, resolve, reject)如果promise和x援用同一个对象,用一个TypeError作为起因来回绝promise如果x是一个promise,依据x的状态: 2.1. 如果x是pending,promise必须放弃期待状态,直到x被解决或回绝 2.2. 如果x是fulfilled,用雷同的value解决promise 2.3. 如果x是rejected,用雷同的reason回绝promise否则,如果x是一个对象或函数 ...

December 26, 2021 · 5 min · jiezi

关于promise:asyncawait和Promise的区别

async/await和Promise都是异步办法,async/await能更好的解决then链async/await采纳同步的思维解决异步办法栗子:我的项目中遇到的一个问题,mounted时须要获取到全副的报警类型后,再获取已抉择的报警类型,最后做的是先调用getAllAlarmTypeList(),而后50ms的延时再调用getAlarmCheckedList()就不会报错,后通过asynce/await能够优化,且更容易了解。 应用Promise办法mounted() {this.getAllAlarmTypeList()setTimeout(this.handleAlarmTrendData, 50)},// 获取所有的报警类型 getAllAlarmTypeList() { let param = { subsystemType: 'XXXXX' } Api.getStatisticalAlarmTypes(param).then((res) => { if(res.value.length > 0) { // 代码 }) this.getAlarmCheckedList() } })},// 获取已抉择的报警类型 getAlarmCheckedList() { let param = { subsystemType: 'XXXXX' } Api.getCheckedAlarmTypeList(param).then(res => { if(res.value.length > 0) { // 代码 } })},应用async/await办法:// 获取所有的报警类型 getAllAlarmTypeList() { return new Promise(async (resolve, reject) => { let param = { subsystemType: 'XXXX' } try { let res = await Api.getStatisticalAlarmTypes(param) if(res.value.length > 0) { res.value = res.value.map((item) => { // 代码 }) resolve(res.value) } else { resolve([]) } } catch (error) { console.error('获取报警类型失败' + error) } })},// 获取已抉择的报警类型 ...

December 13, 2021 · 2 min · jiezi

关于promise:Promiseall-Promiserace-以及ES2020-新特性-PromiseallSettled

更多文章详见公众号【前端css和js干货】Promise是Es6最令人兴奋的个性,在那之前人们可能会通过第三方库如Bluebird或Q应用过promise,然而直到2015年才成为js的规范个性。Promise 对象代表一个异步操作的实现(或失败)以及他的后果值。Promise 会做一些可能须要一些工夫的事件,但最终它会在一切顺利时resolve,或者如果呈现问题则reject。 而后应用.then 和 .catch别离对result或error做一些事件。 const promise = new Promise(function(resolve, reject) { setTimeout(() => { if (Math.random(1) > 0.5) { resolve('Some value'); } else { reject('Some error'); } }, 500);});promise.then((response) => { console.log(response);}).catch(err => { console.log(err);});ES2020 带来了一个新个性:Promise.allSettled。补救了 ES6 Promise.all 和 Promise.race留下的缺点。 promises数组咱们常常不仅要解决一个 Promise,还要解决一系列的 Promise。 如果一次发送许多服务器申请,可能就是这种状况。 例如,我创立了一个蕴含五个 Promise 的数组。 在一段时间(100 毫秒、200 毫秒、……、500 毫秒)后,它们以 50% 的概率rsolve或rejected。 const arrayOfPromises = [];const arrayOfResponses = [];for (let i = 0 ; i < 5; i += 1) { arrayOfPromises.push(new Promise((resolve, reject) => { setTimeout(() => { if (Math.random(1) > 0.5) { resolve('Some value'); } else { reject('Some error'); } }, i * 100); }));}arrayOfPromises.forEach((promise) => { promise.then((response) => { arrayOfResponses.push(response); }).catch((err) => { arrayOfResponses.push(err); });});咱们能够手动解决这个 Promise 数组,在示例中将后果存储在arrayOfResponses数组中。 如果半秒后在控制台中显示数组,将看到如下内容: ...

November 30, 2021 · 1 min · jiezi

关于promise:await-与-Promiseall-结合使用

当遇到多个能够同时执行的异步工作时,就须要应用 Promise.all。 Promise.all 办法用于将多个 Promise 实例,包装成一个新的 Promise 实例。const p = Promise.all([p1, p2, p3])Promise.all 办法承受一个数组作为参数,p1、p2、p3 都是 Promise 实例,如果不是,就会先调用 Promise.resolve 办法,将参数转为 Promise 实例,再进一步解决。(Promise.all 办法的参数能够不是数组,但必须具备 Iterator 接口,且返回的每个成员都是 Promise 实例。) 而 async/await 自身就是 promise 的语法糖,因而能够与 Promise.all 联合应用: const p1 = async () => {}const p2 = async () => {}const p3 = async () => {}const [result1, result2, result3] = await Promise.all([p1, p2, p3])console.log(result1)console.log(result2)console.log(result3)

August 29, 2021 · 1 min · jiezi

关于promise:Promiseall和promiserace的应用场景举例

问题形容为了解决前端异步函数多层嵌套会产生回调天堂问题,以及回调天堂谬误不不便捕获的问题。所以,那些制作规定的大佬们,就在ES6中退出了一个新性能~Promise。本文次要记录一下Promise.all和promise.race的利用场景并举例说明。 对于Promise的基本概念什么的,这里就不赘述了。Promise.all办法简而言之:Promise.all( ).then( )实用于解决多个异步工作,且所有的异步工作都失去后果时的状况。比方:用户点击按钮,会弹出一个弹出对话框,对话框中有两局部数据出现,这两局部数据别离是不同的后端接口获取的数据。弹框弹出后的初始状况下,就让这个弹出框处于数据加载中的状态,当这两局部数据都从接口获取到的时候,才让这个数据加载中状态隐没。让用户看到这两局部的数据。那么此时,咱们就需要这两个异步接口申请工作都实现的时候做解决,所以此时,应用Promise.all办法,就能够轻松的实现,咱们来看一下代码写法 代码附上<template> <div class="box"> <el-button type="primary" plain @click="clickFn">点开弹出框</el-button> </div></template><script>export default { name: "App", methods: { clickFn() { this.alertMask = true; // 关上弹出框 this.loading = true; // 临时还没数据,所以就出现loading加载中成果 // 第一个异步工作 function asyncOne() { let async1 = new Promise(async (resolve, reject) => { setTimeout(() => { // 这里咱们用定时器模仿后端发申请的返回的后果,毕竟都是异步的 let apiData1 = "第一个接口返回数据啦"; resolve(apiData1); }, 800); }); return async1; } console.log("异步工作一", asyncOne()); // 返回的是一个Promise对象 // 第二个异步工作 function asyncTwo() { let async2 = new Promise(async (resolve, reject) => { setTimeout(() => { let apiData2 = "第二个接口返回数据啦"; resolve(apiData2); }, 700); }); return async2; } console.log("异步工作二", asyncTwo()); // 返回的是一个Promise对象 let paramsArr = [asyncOne(), asyncTwo()] // Promise.all办法接管的参数是一个数组,数组中的每一项是一个个的Promise对象 // 咱们在 .then办法外面能够取到 .all的后果。这个后果是一个数组,数组中的每一项 // 对应的就是 .all数组中的每一项的申请后果返回的值 Promise .all(paramsArr) .then((value) => { console.log("Promise.all办法的后果", value); this.loading = true; // 当初有数据了,所以就敞开loading加载中成果 }); }, },};</script>打印的后果图 ...

August 21, 2021 · 2 min · jiezi

关于promise:JS-原生方法原理探究十如何手写实现-PromiseA-及其方法

这是 JS 原生办法原理探索系列的第十篇文章。本文会介绍如何手写一个合乎 Promise A+ 标准的 Promise,并顺带实现 Promise 的相干办法。实现 Promise/A+术语为了更好地浏览本文,先约定一些术语和说法: promise 初始的时候状态还没有落定,处于 pending 状态;它能够落定为 resolved 状态(fulfilled 状态),用 value 示意它 resolve 的值;也能够落定为 rejected 状态,用 reason(拒因)示意它 reject 的值。then 办法承受的胜利回调函数称为 onFulfilled,失败回调函数称为 onRejected实现 Promise 构造函数咱们先尝试实现一个根底的 Promise 构造函数。 首先,用三个常量示意 promise 实例的状态: const PENDING = 'pending'const FULFILLED = 'fulfilled'const REJECTED = 'rejected'Promise 构造函数的作用是创立一个 promise 实例。对于一个 promise 实例来说,它会有几个根本的属性:status 记录 promise 的状态(初始为 pending),value 记录 promise resolve 的值(初始为 null),reason 记录 promise reject 的值(初始为 null)。 咱们别离在 Promise 构造函数中进行定义: ...

August 1, 2021 · 14 min · jiezi

关于promise:promise-和-Observable-的区别

StackOverflow 上的探讨:What is the difference between Promises and Observables? 得赞最高的一个答复:1777 赞 当异步操作实现或失败时,Promise 会解决单个事件。 留神:有 Promise 库反对 cancellation 操作,但 ES6 Promise 到目前为止还不反对。 Observable一个 Observable 就像一个 Stream(在许多语言中),容许传递零个或多个事件,其中为每个事件调用回调。 通常 Observable 比 Promise 更受欢迎,因为它提供了 Promise 的个性等等。应用 Observable,您是否要解决 0、1 或多个事件并不重要。您能够在每种状况下应用雷同的 API。 Observable 还比 Promise 具备可勾销的劣势。如果不再须要对服务器的 HTTP 申请或其余一些低廉的异步操作的后果,Observable 的订阅容许勾销订阅,而 Promise 最终会调用胜利或失败的回调,即便你不这样做不再须要告诉或它提供的后果。 尽管 Promise 会立刻启动,但 Observable 只有在您订阅它时才会启动。这就是为什么 Observable 被称为懈怠的起因。 Observable 提供了 map、forEach、reduce 等运算符,用法相似于数组。 还有一些弱小的操作符,如 retry() 或 replay() 等,它们通常十分不便。 提早执行容许在通过订阅执行 observable 之前建设一系列操作符,以进行更具申明性的编程。 排名第二的答复:374 赞 举例说明。 Angular 应用 Rx.js Observables 而不是 promises 来解决 HTTP。 ...

July 22, 2021 · 2 min · jiezi

关于promise:如何使用Promiseall

什么是 PromisePromise 是一个对象,它代表了一个异步操作的最终实现或者失败及其后果值。简略地说,Promise是一个示意当前某个时候会生成的值的占位符。Promise 是解决异步操作的十分有用的对象。JavaScript 提供了一个帮忙函数 Promise.all(promisesArrayOrIterable) 反对并行处理多个Promises,并且在一个聚合数组中获取后果值。我么一起来学习下Promise.all()是怎么工作的。 1.Promise.all()Promise.all() 是内置的帮忙函数可能承受promise数组,函数返回的格局如下: const allPromise = Promise.all([promise1, promise2, ...]); 而后能够应用 then-able 语法提取Promise返回的值: `allPromise.then(values => { values; // [valueOfPromise1, valueOfPromise2, ...]}).catch(error => { error; // rejectReason of any first rejected promise});` 或者应用async/await 语法:`try { const values = await allPromise; values; // [valueOfPromise1, valueOfPromise2, ...]} catch (error) { error; // rejectReason of any first rejected promise}` 最有用的是Promise 能够通过 Promise.all() 获取resolved 或rejected 如果所有的promise对象申请胜利了,那么 allPromise 是由Promise 组成的数组。Promises的返回程序与入数组栈的程序无关。 ...

July 13, 2021 · 1 min · jiezi

关于promise:Promiseprototypedone

promise设计规格并没有对 Promise.prototype.done做出任何规定,因而在应用的时候,你能够应用已有类库提供的实现,也能够本人去实现。 作用无论Promise对象的回调链以then办法还是catch结尾,只有最初一个办法抛出谬误,就有可能无奈捕捉到(因为Promise外部的谬误不会冒泡到全局),因而,须要提供一个done办法,它总是处于回调链的尾端(被执行),保障抛出任何可能呈现的谬误; 示例代码"use strict";if (typeof Promise.prototype.done === "undefined") { Promise.prototype.done = function (onFulfilled, onRejected) { this.then(onFulfilled, onRejected).catch(function (error) { setTimeout(function () { throw error; }, 0); }); };}done 有以下两个特点1、done 中呈现的谬误会被作为异样抛出2、终结 Promise chain

May 25, 2021 · 1 min · jiezi

关于promise:使用Promiserace实现超时机制取消XHR请求

咱们来看一下如何应用Promise.race来实现超时机制。当然XHR有一个 timeout 属性,应用该属性也能够简略实现超时性能,然而为了能反对多个XHR同时超时或者其余性能,咱们采纳了容易了解的异步形式在XHR中通过超时来实现勾销正在进行中的操作。 让Promise期待指定工夫首先咱们来看一下如何在Promise中实现超时。所谓超时就是要在通过肯定工夫后进行某些操作,应用 setTimeout 的话很好了解。首先咱们来串讲一个单纯的在Promise中调用 setTimeout 的函数。 //delayPromise.jsfunction delayPromise(ms) { return new Promise(function (resolve) { setTimeout(resolve, ms); });}Promise.race中的超时Promise.race就是一组promise中只有有一个返回,其它的promise就不会执行后续回调(无论谬误还是胜利) var winnerPromise = new Promise(function (resolve) { setTimeout(function () { console.log('this is winner'); resolve('this is winner'); }, 4); });var loserPromise = new Promise(function (resolve) { setTimeout(function () { console.log('this is loser'); resolve('this is loser'); }, 1000); });// 第一个promise变为resolve后程序进行,第二个promise不会进入回调Promise.race([winnerPromise, loserPromise]).then(function (value) { console.log(value); // => 'this is winner'});咱们能够将方才的 delayPromise 和其它promise对象一起放到 Promise.race 中来是实现简略的超时机制。 ...

May 24, 2021 · 3 min · jiezi

关于promise:promise内部实现

从本文你将理解到什么是promisepromise的外部实现 resolve实例属性reject实例属性then办法then办法的屡次调用then的链式调用谬误捕捉try{}catch(e){}then可选参数静态方法 all 的实现静态方法 resolve 的实现实例办法 finally 的实现实例办法 catch 的实现 什么是promise步骤剖析 /** * 1.Promise是个类,参数是个回调函数(执行器),这个执行器会立刻执行 * 2.promise中有三种状态,胜利fulfilled,失败rejected,期待pending,一旦状态确定,则不可更改 * pending -> fulfilled * pending -> rejected * 3.resolve,reject函数是用来扭转状态的 * resolve() pending -> fulfilled * reject() pending -> rejected * 4.then办法外部做的事件就是判断状态 如果状态是胜利调用胜利的回调函数,如果是失败调用失败的回调函数, * then办法是被定义在原型对象中的 * 5.then胜利的回调函数有个参数,示意胜利之后的值。then失败的回调函数有个参数,示意失败之后的起因*/new Promise((resolve,reject)=>{resolve("胜利")// reject("失败")})实现实现最根底的性能申明一个 MyPromise 类,在构造函数中接管一个执行器,并立刻执行这个执行器 // 定义状态常量const PENDING = 'pending'; // 期待const FULFILLED = 'fulfilled'; // 胜利const REJECTED = 'rejected'; // 失败class MyPromise { constructor(executor) { // 执行器接管两个参数,resolve 和 reject // 执行器立刻执行 executor(this.resolve, this.reject); } // 定义实例的状态属性: 初始值为 pending,状态一旦确定,就不可更改 status = PENDING;}执行 执行器 的时候,须要传递两个参数,resolve 和 reject。 ...

May 18, 2021 · 8 min · jiezi

关于promise:前端笔记Promise

Promise executorPromise 对象的结构器(constructor)语法如下: let promise = new Promise(function(resolve, reject) { // executor(生产者代码)});传递给 new Promise 的函数被称为 executor。当 new Promise 被创立,executor 会主动运行,它蕴含最终应产出后果的生产者代码。由 new Promise 结构器返回的 promise 对象具备以下外部属性: state — 最后是 "pending",而后在 resolve 被调用时变为 "fulfilled",或者在 reject 被调用时变为 "rejected"。 result — 最后是 undefined,而后在 resolve(value) 被调用时变为 value,或者在 reject(error)被调用时变为 error。留神: executor 只能调用一个 resolve 或一个 reject。任何状态的更改都是最终的。后续的 resolve 和 reject 的调用都会被疏忽。 resolve/reject 只须要一个参数(或不蕴含任何参数),并且将疏忽额定的参数 Resolve/reject 能够立刻进行:executor 通常是异步执行某些操作,并在一段时间后调用 resolve/reject,但这不是必须的。能够立刻调用 resolve 或 reject。 Promise 对象的 state 和 result 属性都是外部的,无奈间接拜访它们。但能够对它们应用 .then/.catch/.finally 办法。 ...

April 9, 2021 · 2 min · jiezi

关于promise:Promise-A-规范

听名字莫名其妙,齐全没听说过,然而面试官就非要问。Promise标准很多,有A/B/D/A+,然而ES6用的是A+ 标准解读一个promise的以后状态只能是pending、fullfilled和rejected三种,状态扭转之恩那个是pending->fullfilled或pending->rejected,状态扭转不可逆Promise的then办法承受两个参数,示意promise状态产生扭转时的回调(onFullfilled,onRejected),then办法返回一个promise,then能够被一个promise屡次调用标准解读结束... Promise原理雏形 function Promise(fn) { let value = null, callbacks = [] // 观察者模式,注册事件 this.then = function(onFullfilled) { callbacks.push(onFullfilled) } function resolve(value) { callbacks.forEach(function(callback){ callback(value) }) } fn(reslove)}

April 8, 2021 · 1 min · jiezi

关于promise:Promiseresolve与new-Promiser-rv

Promise.resolve办法的参数分成四种状况。参数是一个 Promise 实例如果参数是 Promise 实例,那么Promise.resolve将不做任何批改、一成不变地返回这个实例。这是一个非凡的状况会和另一种new Promise(r => r(v))产生不一样的成果,最初阐明 参数是一个thenable对象thenable对象指的是具备then办法的对象,比方上面这个对象 let thenable = { then: function(resolve, reject) { resolve(42); }};Promise.resolve办法会将这个对象转为 Promise 对象,而后就立刻执行thenable对象的then办法。 let thenable = { then: function(resolve, reject) { resolve(42); }};let p1 = Promise.resolve(thenable);p1.then(function(value) { console.log(value); // 42});thenable对象的then办法执行后,对象p1的状态就变为resolved,从而立刻执行最初那个then办法指定的回调函数,输入 42 参数不是具备then办法的对象,或基本就不是对象如果参数是一个原始值,或者是一个不具备then办法的对象,则Promise.resolve办法返回一个新的 Promise 对象,状态为resolved。 const p = Promise.resolve('Hello');p.then(function (s){ console.log(s)});// Hello因为字符串Hello不属于异步操作(判断办法是字符串对象不具备 then 办法),返回 Promise 实例的状态从毕生成就是resolved,所以回调函数会执行。Promise.resolve办法的参数,会同时传给回调函数 不带有任何参数Promise.resolve办法容许调用时不带参数,间接返回一个resolved状态的 Promise 对象。 setTimeout(function () { console.log('three');}, 0);Promise.resolve().then(function () { console.log('two');});console.log('one');// one// two// three.then()函数里不返回值或者返回的不是promise,那么 then 返回的 Promise 将会成为承受状态(resolve) ...

March 18, 2021 · 1 min · jiezi

关于promise:又开始了rxjs-与promise的比较

1.promise 的 resolve 与 reject,任何时候都是异步的,会将回调放在下一个微工作的循环中调用 let nn = Promise.resolve('niahoP1');nn.then(console.log)console.log('niahoP2')// niahoP2 niahoP12.rxjs 的 subscribe 是同步的,流更新之后立即就会同步调用订阅者 let su = new rxjs.Subject()console.log('nihao1')su.subscribe(console.log);su.next('nihao3')console.log('nihao2')// nihao1 nihao3 nihao2// 很遗憾,然而事实就是这样的3.流解决中产生了任何的未捕捉的谬误,都会导致这个流挂掉,前面再更新流无奈触发订阅 let {map, switchMap} = rxjs.operators;let { Subject } = rxjs;let ss = new Subject();ss.pipe( map(value => { console.log(value, Object.keys(value), 'keys') // console.log(value.b.c) // 失常状况 > ss 2 // 放开这一行会有一个执行谬误,这时后果为 TypeError: Cannot read property 'c' of undefined return value })).subscribe({ complete: console.log, error: console.log, next: console.log})ss.next('ss')setTimeout(() => {ss.next(2)}, 2000)4.完犊子,这里代码我看不懂了,如果subject的订阅是同步的,那为什么宏工作中报错的代码,下一行代码还会继续执行呢?我傻了,,,http://jsrun.net/pzaKp/edit ...

February 5, 2021 · 1 min · jiezi

关于promise:每日一题面试官问你对Promise的理解可能是需要你能手动实现各个特性

关注公众号「松宝写代码」,精选好文,每日一题 退出咱们一起学习,day day up 作者:saucxs | songEagle起源:原创 一、前言2020.12.23日刚立的flag,每日一题,题目类型不限度,能够是:算法题,面试题,论述题等等。 往期「每日一题」: 第2道[「[每日一题]ES6中为什么要应用Symbol?」](https://mp.weixin.qq.com/s/om...第1道「一道面试题是如何引发深层次的灵魂拷问?」接下来是第3道:谈谈你对 promise 的了解? 二、谈谈你对 promise 的了解?1、咱们简略概括一下promisePromise 是 ES6 新增的语法,解决了回调天堂的问题。 无论是ES6的Promise也好,jQuery的Promise也好,不同的库有不同的实现,然而大家遵循的都是同一套标准,所以,Promise并不指特定的某个实现,它是一种标准,是一套解决JavaScript异步的机制。 Promise 实质上就是一个绑定了回调的对象,而不是将回调传回函数外部。 所以,Promise在肯定水平上解决了回调函数的书写构造问题,但回调函数仍然在主流程上存在,只不过都放到了then(...)外面,和咱们大脑程序线性的思维逻辑还是有出入的。 2、咱们说一下promise相干标准能够把 Promise 看成一个状态机。初始是 pending 状态,能够通过函数 resolve 和 reject ,将状态转变为 resolved 或者 rejected 状态,状态一旦扭转就不能再次变动。then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 标准规定除了 pending 状态,其余状态是不能够扭转的,如果返回的是一个雷同实例的话,多个 then 调用就失去意义了。then 办法能够被同一个 promise 调用屡次。值穿透三、promise 是如何实现的?1、Promise的简略应用 咱们通过这种应用构建Promise实现的最后版本 2、Promise的大抵框架大抵框架有了,然而Promise状态,resolve函数,reject函数,以及then等回调没有具体解决 3、Promise的链式存储咱们先看一个例子: 每距离1秒打印一个数字,哈哈,这个不是实在的距离1秒,汪汪, 这个的输入是啥? 打印程序:1、2、3 这里咱们能确认的是: 让a,b,c的只能在then的回调接管到在间断的异步调用中,如何保障异步函数的执行程序Promise一个常见的需要就是间断执行两个或者多个异步操作,这种状况下,每一个起初的操作都在后面的操作执行胜利之后,带着上一步操作所返回的后果开始执行。这里用setTimeout来解决. 4、Promise的状态机制和执行程序为了保障Promise的异步操作时的程序执行,这里给Promise加上状态机制 5、Promise的递归执行每个Promise前面链接一个对象,该对象蕴含onresolved,onrejected,子promise三个属性. 当父Promise 状态扭转结束,执行完相应的onresolved/onrejected的时候,拿到子promise,在期待这个子promise状态扭转,在执行相应的onresolved/onrejected。顺次循环直到以后promise没有子promise。 ...

December 26, 2020 · 1 min · jiezi

关于promise:一次写过瘾手写Promise全家桶Generatorasyncawait

观感度:???????????????????? 口味:海底捞 烹饪工夫:15min 本文已收录在前端食堂同名仓库Github github.com/Geekhyt,欢迎光临食堂,如果感觉酒菜还算可口,赏个 Star 对食堂老板来说是莫大的激励。手写 Promise 全家桶Promise/A+ 标准镇楼! 如果你没读过 Promise/A+ 标准也没关系,我帮你总结了如下三局部重点: 不过倡议看完本文后还是要亲自去读一读,不多 bb,开始展现。 标准重点1.Promise 状态Promise 的三个状态别离是 pending、fulfilled 和 rejected。 pending: 待定,Promise 的初始状态。在此状态下能够落定 (settled) 为 fulfilled 或 rejected 状态。fulfilled: 兑现(解决),示意执行胜利。Promise 被 resolve 后的状态,状态不可再扭转,且有一个公有的值 value。rejected: 回绝,示意执行失败。Promise 被 reject 后的状态,状态不可再扭转,且有一个公有的起因 reason。留神:value 和 reason 也是不可变的,它们蕴含原始值或对象的不可批改的援用,默认值为 undefined。 2.Then 办法要求必须提供一个 then 办法来拜访以后或最终的 value 或 reason。 promise.then(onFulfilled, onRejected)1.then 办法承受两个函数作为参数,且参数可选。2.如果可选参数不为函数时会被疏忽。3.两个函数都是异步执行,会放入事件队列期待下一轮 tick。4.当调用 onFulfilled 函数时,会将以后 Promise 的 value 值作为参数传入。5.当调用 onRejected 函数时,会将以后 Promise 的 reason 失败起因作为参数传入。6.then 函数的返回值为 Promise。7.then 能够被同一个 Promise 屡次调用。3.Promise 解决过程Promise 的解决过程是一个形象操作,接管一个 Promise 和一个值 x。 ...

December 19, 2020 · 8 min · jiezi

关于promise:前端面试每日-31-第598天

明天的知识点 (2020.12.04) —— 第598天 (我也要出题)[html] 你有应用过del标签吗?说说它的用处[css] css变量辨别大小写吗?[js] promise有哪几种状态?是如何变动的?[软技能] 除了工作外,你为团队还做过哪些奉献?《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

December 4, 2020 · 1 min · jiezi

关于promise:前端面试每日-31-第596天

明天的知识点 (2020.12.02) —— 第596天 (我也要出题)[html] 你有应用过ins标签吗?说说它的用处[css] css变量和预处理器中的变量有什么不同?[js] 应用js实现一个并发限度的promise,并保障最多同时运行三个工作[软技能] 你有本人或者为公司写过专利吗?《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

December 2, 2020 · 1 min · jiezi

关于promise:Promise使用

Promise应用1.开发环境 uni-app2.电脑系统 windows10专业版3.在开发的过程中,咱们都是会应用到Promise,如果说你当初还不晓得什么是Promise,为什么应用Promise,那么你真的落后啦!上面我来分享一下Promise应用办法,心愿对你有所帮忙!4.uni-app数据申请,uni-app提供的uni.request({})进行简略的Promise封装,在methods中增加代码如下: chengeth5(){ return new Promise((resolve,reject)=>{ uni.request({ url:'/api/feng', method:'post', data:this.ChenindexconOnj, success:(res)=>{ // console.log(res);//输入申请到的数据 resolve(res);//resolve作用是,将Promise对象的状态从“未实现”变为“胜利”(即从 pending 变为 resolved),在异步操作胜利时调用,并将异步操作的后果,作为参数传递进来。这个是一个重点,切记不要遗记写! }, }) }) },5.在methods中再定义一个办法,代码如下: async CChen(){ let ok=await this.chengeth5(); console.log(ok); },6.在浏览器中输入后果,成果如下:7.本期的分享到了这里就完结啦,是不是很nice,让咱们一起致力走向巅峰!

November 18, 2020 · 1 min · jiezi

关于promise:Promise三种状态

三种状态1.pending:在过程中还没有后果2.resolved:胜利3.rejected:失败 状态变动1、pending -> resolved2、pending -> rejected 状态的体现pending状态不会触发then和catchresolved状态会触发后续的then回调函数rejected状态会触发后续的catch回调函数 then和catch扭转状态then失常状况下会返回resolved,报错则返回rejectedcatch失常状况下会返回resolved,报错则返回rejected 测试题//第一题(后果会打印进去1,3,返回resolved状态)Promise.resolve().then(()=>{ console.log(1) //1 resolved}).catch(()=>{ console.log(2)}).then(()=>{ console.log(3) // 3 resolved})//第二题(后果会打印进去1,2,3)Promise.resolve().then(()=>{ console.log(1) //1 throw new Error("error1") //rejected}).catch(()=>{ console.log(2) //2 resolved}).then(()=>{ console.log(3) //3 resolved})//第三题(后果会打印进去1,2)Promise.resolve.then(()=>{ console.log(1) //1 throw new Error("error1") //rejected}).catch(()=>{ console.log(2) //2 resolved}).catch(()=>{ console.log(3)})

September 5, 2020 · 1 min · jiezi

关于promise:前端面试每日-31-第490天

明天的知识点 (2020.08.18) —— 第490天 (我也要出题)[html] 你最喜爱html的哪个标签?为什么?[css] 应用css3制作下雨动画的成果[js] 如何勾销promise?[软技能] 请说说扫码登录的原理及流程《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

August 18, 2020 · 1 min · jiezi

关于promise:PromisesA规范

Promises/A+标准一个凋谢的规范,实现者为开发者提供的可操作JavaScript promise。promise代表着异步操作最终后果。和promise交互的次要形式是通过其then办法,then办法通过注册回调以承受promise的最终值或者promise无奈实现的起因。该标准详细描述了then办法的行为,提供了一个可互操作的根底,所有Promises/A+统一的promise实现都能够依赖于它来提供。因而,标准应该被认为是十分稳固的。只管Promises/A+组织偶然会对本标准进行一些小的向后兼容更改,以解决新发现的小问题,但只有通过认真思考、探讨和测试后,咱们才会集成较大或向后不兼容的更改。从历史上看,Promises/A+说明了晚期Promise/A提案的行为条款,将其扩大到涵盖事实上的行为,并省略了未指定或有问题的局部。最初,外围Promises/A+标准不波及如何创立、实现或回绝promise,而是抉择集中于提供可互操作的then办法。将来在配套标准中的工作可能会波及到这些主题。 1.术语“promise”是then行为合乎本标准的对象或函数。“ thenable”是定义then办法的对象或函数。“值”是任何非法的JavaScript值(包含undefined,ableable或promise)。“ exception”是应用该throw语句引发的值。“起因”是一个值,该值批示为什么回绝promise。2.要求2.1 promise状态promise必须处于以下三种状态之一:待定,已实现或被回绝。 待定时,promise:可能会转换为已实现或已回绝状态。当实现时,promise: 不得过渡到其余任何状态。必须具备一个值,该值不能更改。当被回绝时,promise: 不得过渡到其余任何状态。必须有一个理由,不能扭转。在此,“不得更改”是指不变的身份(即===),但并不示意深层的不变性。2.2 then办法promise必须提供一种then拜访其以后或最终价值或起因的办法。允诺的then办法承受两个参数: promise.then(onFulfilled, onRejected)这两个onFulfilled和onRejected可选的参数: 如果onFulfilled不是函数,则必须将其疏忽。如果onRejected不是函数,则必须将其疏忽。如果onFulfilled是一个函数: 必须在promise实现后调用,以promise的值作为第一个参数。在promise实现之前肯定不能调用它。不能屡次调用它。如果onRejected是一个性能, 必须在promise被回绝之后以promise的理由作为第一个参数调用它。在promise被回绝之前不能调用它。不能屡次调用它。onFulfilled或onRejected在执行上下文堆栈仅蕴含平台代码之前不得调用。[ 3.1 ]。onFulfilled并且onRejected必须作为函数调用(即没有this值)。[ 3.2 ]then 可能在同一诺言中屡次被调用。 如果/何时promise实现,则所有各自的onFulfilled回调必须依照其对的原始调用的程序执行then。如果/何时promise被回绝,则所有各自的onRejected回调必须依照其对的原始调用的程序执行then。then必须返回一个promise[ 3.3 ]。 如果有一个onFulfilled或onRejected返回一个值x,请运行Promise Resolution Procedure [[Resolve]](promise2, x)。如果任何一个onFulfilled或onRejected引发异样e,则promise2必须e以其为理由予以回绝。如果onFulfilled不是函数且promise1已实现,则promise2必须应用与雷同的值来实现promise1。如果onRejected不是性能而promise1被回绝,则promise2必须以与雷同的理由将其回绝promise1。 promise2 = promise1.then(onFulfilled, onRejected);2.3promise解决程序promise解决过程是一个形象的操作作为输出一个promise和一个值,它示意咱们作为[[Resolve]](promise, x)。如果x是可能的,则在行为至多相似于诺言的假如下,尝试promise采纳的状态。否则,它将满足value 。xxpromisex只有对约定的实现公开Promises / A +兼容的then办法,对约定的实现就能够实现互操作。它还容许Promises / A +实现以正当的then办法“异化”不合格的实现。要运行[[Resolve]](promise, x),请执行以下步骤: 如果promise和x援用雷同的对象,promise则以回绝TypeError为理由。如果x是一个promise,则采纳其状态[ 3.4 ]: 如果x未决,则promise必须放弃未决状态,直到x实现或被回绝。如果/何时x满足,promise则以雷同的值满足。如果/何时x被回绝,promise则以雷同的理由回绝。否则,如果x是对象或函数, then是x.then。[ 3.5 ]如果检索属性x.then中抛出的异样的后果e,回绝promise与e作为的起因。如果then是函数,请应用xas this,第一个参数resolvePromise和第二个参数进行调用rejectPromise,其中: 如果/何时resolvePromise应用值调用y,请运行[[Resolve]](promise, y)。如果/当rejectPromise是带一个理由r,回绝promise与r。如果同时调用resolvePromise和rejectPromise,或者对同一参数进行了屡次调用,则第一个调用优先,而所有其余调用均被疏忽。如果调用then引发异样e, 如果resolvePromise或rejectPromise曾经被调用,则疏忽它。否则,回绝promise与e作为的起因。如果then不是一个函数,实现promise用x。如果x不是一个对象或性能,实现promise与x。如果应用参加循环的可循环链的可扩大物解决了诺言,使得[[Resolve]](promise, thenable)最终的递归性质最终导致[[Resolve]](promise, thenable)再次被调用,则遵循上述算法将导致有限递归。激励但不是必须的实现,以检测这种递归并promise以提供信息TypeError的理由回绝。[ 3.6 ] 3.笔记这里的“平台代码”是指引擎,环境和promise实现代码。实际上,此要求可确保在调用事件循环之后并应用新堆栈onFulfilled并onRejected异步执行then。这能够通过“宏工作”机制(例如setTimeout或setImmediate)或“微工作”机制(例如MutationObserver或)来实现process.nextTick。因为promise实现被视为平台代码,因而它自身可能蕴含一个任务调度队列或“蹦床”,在其中调用处理程序。也就是说,在严格模式下this将呈现undefined在其中;在懒惰(sloppy)模式下,它将是全局对象。promise2 === promise1如果实现满足有要求,则实现能够容许。每个实现都应记录其是否能够生产promise2=== promise1以及在什么条件下生产。通常,只有x它来自以后的实现,才晓得这是一个真正的promise。本节容许应用特定于实现的形式来采纳已知合乎promise的状态。首先存储对的援用x.then,而后测试该援用,而后调用该援用的过程防止了对该x.then属性的屡次拜访。此类预防措施对于确保访问者属性的一致性十分重要,因为访问者属性的值在两次检索之间可能会发生变化。实现应不设置thenable链的深度任何限度,并假如超出任何限度递归将是有限的。只有真正的周期能力导致TypeError; 如果遇到有限多个不同的罐头,则永远递归是正确的行为。原文地址

August 10, 2020 · 1 min · jiezi

关于promise:研究当resolve函数参数里是另外一个Promise实例

起因明天遇到一个题目,钻研了一天,从新复习了一遍Promise实现,得进去一个不算论断的论断...... 这个题通过我的化简长成这样,求代码后果 const p = Promise.resolve()new Promise(resolve => { new Promise(resolve => {resolve(p)}) .then(() => { console.log('after:await') })})let p1 = p.then(() => { console.log('tick:a')})let p2 = p1.then(() => { console.log('tick:b')})let p3 = p2.then(() => { console.log('tick:c')})/*p.then(() => { console.log('tick:a')}).then(() => { console.log('tick:b')}).then(() => { console.log('tick:c')})*/promise本质咱们晓得excutor同步执行,then外面的代码异步执行,怎么实现的?简略的说(这里以胜利为例),then外面的代码同步增加到所属promise的胜利回调队列,当excutor外面的参数resolve办法执行的时候,会把胜利回调队列外面的代码顺次执行 对resolve参数的解决如果excutor的resolve办法承受的参数是一个Promise对象会怎么样?首先我在promiseA+标准外面并没有找到这种状况的规定 而后我看了三个promise库,bluebird,es6-promise,promise,都没有对这种状况作解决,也就是当成一个一般的对象 最初阮一峰的es6外面对这种状况有形容 resolve函数的参数除了失常的值以外,还可能是另一个 Promise 实例,这时p1的状态就会传递给p2,p2的回调函数就会期待p1的状态扭转应该是新增加的Promise的规定 已有的材料的相干实现在网上看了很多Promise实现,根本对promise的excutor的resolve办法的实现是这样的promise实现。对参数如果是Promise都是等promise执行完再调resolve constructor(executor){ ... let resolve = (value) =>{ // 如果resolve的值时一个promise if(value instanceof Promise){ // 我就让这个promise执行,把胜利的后果再次判断 return value.then(resolve,reject) //参数会主动传递到resolve外面去 } if (this.status === 'pending') { this.status = 'fulfilled' this.value = value this.onFulfilledCallback.forEach(fn => fn(this.value)) } } ...留神,这种实现能跑过PromiseA+标准测试用例!从A+标准的角度没有问题 ...

July 28, 2020 · 1 min · jiezi

关于promise:手写Promise-实例方法catchfinally

手写Promise - 实现一个根底的Promise手写Promise - 实例办法catch、finally手写Promise - 罕用静态方法all、any、resolve、reject、race 上一篇文章手写了一个根底的Promise,咱们持续来欠缺它,为其增加常常用到的catch和finally办法 catch & finallycatch() 办法返回一个Promise,并且解决回绝的状况。咱们晓得then办法的第二个参数其实就是干这个用的,catch只是一个别名。 finally() 办法返回一个Promise。在promise完结时,无论后果是fulfilled或者是rejected,都会执行指定的回调函数。和catch一样,也只是对then的一个简写,相当于是传入的函数既是onFulfilled也是onRejected。 有一点须要留神,finally和catch办法只是then的一个别名,实际上返回的还是一个promise,齐全能够这样写:promise.then().finally().then().catch().then() 咱们把上一章节的代码拷过去,而后向外面增加catch和finally办法。 class WPromise { static pending = 'pending'; static fulfilled = 'fulfilled'; static rejected = 'rejected'; constructor(executor) { this.status = WPromise.pending; // 初始化状态为pending this.value = undefined; // 存储 this._resolve 即操作胜利 返回的值 this.reason = undefined; // 存储 this._reject 即操作失败 返回的值 // 存储then中传入的参数 // 至于为什么是数组呢?因为同一个Promise的then办法能够调用屡次 this.callbacks = []; executor(this._resolve.bind(this), this._reject.bind(this)); } // onFulfilled 是胜利时执行的函数 // onRejected 是失败时执行的函数 then(onFulfilled, onRejected) { // 返回一个新的Promise return new WPromise((nextResolve, nextReject) => { // 这里之所以把下一个Promsie的resolve函数和reject函数也存在callback中 // 是为了将onFulfilled的执行后果通过nextResolve传入到下一个Promise作为它的value值 this._handler({ nextResolve, nextReject, onFulfilled, onRejected }); }); } // catch办法只有一个参数用于处理错误的状况 catch(onRejected) { return this.then(null, onRejected); } finally(onFinally) { return this.then(onFinally, onFinally); } _resolve(value) { // 解决onFulfilled执行后果是一个Promise时的状况 // 这里可能了解起来有点艰难 // 当value instanof WPromise时,阐明以后Promise必定不会是第一个Promise // 而是后续then办法返回的Promise(第二个Promise) // 咱们要获取的是value中的value值(有点绕,value是个promise时,那么外部存有个value的变量) // 怎么将value的value值获取到呢,能够将传递一个函数作为value.then的onFulfilled参数 // 那么在value的外部则会执行这个函数,咱们只须要将以后Promise的value值赋值为value的value即可 if (value instanceof WPromise) { value.then( this._resolve.bind(this), this._reject.bind(this) ); return; } this.value = value; this.status = WPromise.fulfilled; // 将状态设置为胜利 // 告诉事件执行 this.callbacks.forEach(cb => this._handler(cb)); } _reject(reason) { if (reason instanceof WPromise) { reason.then( this._resolve.bind(this), this._reject.bind(this) ); return; } this.reason = reason; this.status = WPromise.rejected; // 将状态设置为失败 this.callbacks.forEach(cb => this._handler(cb)); } _handler(callback) { const { onFulfilled, onRejected, nextResolve, nextReject } = callback; if (this.status === WPromise.pending) { this.callbacks.push(callback); return; } if (this.status === WPromise.fulfilled) { // 传入存储的值 // 未传入onFulfilled时,将undefined传入 const nextValue = onFulfilled ? onFulfilled(this.value) : undefined; nextResolve(nextValue); return; } if (this.status === WPromise.rejected) { // 传入存储的错误信息 // 同样的解决 const nextReason = onRejected ? onRejected(this.reason) : undefined; nextReject(nextReason); } }}是的,就是这么简略,当初来测试一下: ...

July 17, 2020 · 2 min · jiezi

手写Promise-实现一个基础的Promise

前端开发中常常会用到Promise,不过有局部人并不分明Promise的原理,本文也是自己在学习Promise时对Promis的一些意识,心愿能对各位童鞋有所帮忙。 手写Promise - 实现一个根底的Promise[手写Promise - 实例办法catch、finally]()[手写Promise - 罕用静态方法all、any、resolve、reject、race]() 从意识Promise开始。。。/* 模仿一个简略的异步行为 */function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('willem'); }, 1000); });}fetchData().then((data) => { // after 1000ms console.log(data); // willem return 'wei';}, (err) => {}).then((data2) => { console.log(data2); // wei});下面的例子算是一个最常见的用法,可能在应用的时候更多的应用的是catch来解决异样来代替then办法的第二个参数,但catch也只是一个then的语法糖。 从中咱们能够用一些句子来形容Promise。 promise是一个类,它的构造函数承受一个函数,函数的两个参数也都是函数在传入的函数中执行resolve示意胜利,执行reject示意失败,传入的值会传给then办法的回调函数promise有一个叫做then的办法,该办法有两个参数,第一个参数是胜利之后执行的回调函数,第二个参数是失败之后执行的回调函数。then办法在resolve或者reject执行之后才会执行,并且then办法中的值是传给resolve或reject的参数promise反对链式调用有了相应的形容,接下来就是来一步一步实现了。 简略版Promise1. promise是一个类,它的构造函数承受一个函数,函数的两个参数也都是函数 第一点比较简单 // 这里没有应用Promise作为类名是为了不便测试class WPromise { constructor(executor) { // 这里绑定this是为了避免执行时this的指向扭转,this的指向问题,这里不过多赘述 executor(this._resolve.bind(this), this._reject.bind(this)); } _resolve() {} _reject() {}}2. 在传入的函数中执行resolve示意胜利,执行reject示意失败,传入的值会传给then办法的回调函数 胜利、失败,这个很容易想到应用一个状态进行标记,实际上Promise就是这样做的。在Promise中应用了pending、fulfilled、rejected来标识以后的状态。 ...

July 11, 2020 · 4 min · jiezi

Promise周边方法

上面了解promise a+之后,那么这篇文章就是扩展一下promise的周边方法promisify的实现promisify是node提供的一个方法用来解决回调嵌套问题,用起来很方便;我项目中有两个文件,一个为name.txt,里面存放的是age.txt,另一个为age.txt里面的内容为18,这里就是为了演示多个异步接收返回值问题,现在让我们看看原来获取异步返回值,毫无疑问这样非常的恶心 原始调用异步let fs= require('fs');fs.readFile('./name.txt', 'utf8', function (err, data) { console.log(data);//age.txt if (err) { console.log(err); } else { fs.readFile(data, 'utf8', function (err, data) { if (err) { console.log(err); } else { console.log(data); } }) }});既然已经看到他的恶心之处了,那我们想办法优化一下,封装一个公共函数: read公共方法的封装function read(...args) { let dfd = Promise.defer();//这个是我们上一账封装的promise中的方法 fs.readFile(...args, function (err, data) { if (err) dfd.reject(err); dfd.resolve(data) }); return dfd.promise}使用的时候如下: read('./name.txt', 'utf8').then(data => { console.log(data); return read(data, 'utf8')}).then(data => { console.log(data);})上面这种看起来不错,但是还是不能让人满意,因为他的功能太单一了,这时候我们的主角promisify就上场了,那么我们先来看一看它原本是怎么用的: promisify使用let {promisify} = require('util');//node提供的方法库let readFile = promisify(fs.readFile); //用什么方法就把对应的方法传进去,肥肠的方便readFile('./name.txt', 'utf8').then(data => { console.log(data);//age.txt return readFile(data, 'utf8')}).then(data => { console.log(data);//18})promisify实现上面的用法很简单,那么我们简单的实现一下吧,用法和以上相同 ...

June 23, 2020 · 2 min · jiezi

书籍翻译-JavaScript并发编程第三章-使用Promises实现同步

本文是我翻译《JavaScript Concurrency》书籍的第三章 使用Promises实现同步,该书主要以Promises、Generator、Web workers等技术来讲解JavaScript并发编程方面的实践。完整书籍翻译地址:https://github.com/yzsunlei/javascript_concurrency_translation 。由于能力有限,肯定存在翻译不清楚甚至翻译错误的地方,欢迎朋友们提issue指出,感谢。 Promises几年前就在JavaScript类库中实现了。这一切都始于Promises/A+规范。这些类库的实现都有它们自己的形式,直到最近(确切地说是ES6),Promises规范才被JavaScript语言纳入。如标题那样 - 它帮助我们实现同步原则。 在本章中,我们将首先简单介绍Promises中各种术语,以便更容易理解本章的后面部分内容。然后,通过各种方式,我们将使用Promises来解决目前的一些问题,并让并发处理更容易。准备好了吗? Promise相关术语在我们深入研究代码之前,让我们花一点时间确保我们牢牢掌握Promises有关的术语。有Promise实例,但是还有各种状态和方法。如果我们能够弄清楚Promise这些术语,那么后面的章节会更易理解。这些解释简短易懂,所以如果您已经使用过Promises,您可以快速看下这些术语,就当复习下。 Promise顾名思义,Promise是一种承诺。将Promise视为尚不存在的值的代理。Promise让我们更好的编写并发代码,因为我们知道值会在将来某个时刻存在,并且我们不必编写大量的状态检查样板代码。 状态(State)Promises总是处于以下三种状态之一: • 等待:这是Promise创建后的第一个状态。它一直处于等待状态,直到它完成或被拒绝。 • 完成:该Promise值已经处理完成,并能为它提供then()回调函数。 • 拒绝:处理Promise的值出了问题。现在没有数据。 Promise状态的一个有趣特性是它们只转换一次。它们要么从等待状态到完成,要么从等待状态到被拒绝。一旦它们进行了这种状态转换,后面就会锁定在这种状态。 执行器(Executor)执行器函数负责以某种方式解析值并将处于等待状态。创建Promise后立即调用此函数。它需要两个参数:resolver函数和rejector函数。 解析器(Resolver)解析器是一个作为参数传递给执行器函数的函数。实际上,这非常方便,因为我们可以将解析器函数传递给另一个函数,依此类推。调用解析器函数的位置并不重要,但是当它被调用时,Promise会进入一个完成状态。状态的这种改变将触发then()回调 - 这些我们将在后面看到。 拒绝器(Rejector)拒绝器与解析器相似。它是传递给执行器函数的第二个参数,可以从任何地方调用。当它被调用时,Promise从等待状态改变到拒绝状态。这种状态的改变将调用错误回调函数,如果有的话,会传递给then()或catch()。 Thenable如果对象具有接受完成回调和拒绝回调作为参数的then()方法,则该对象就是Thenable。换句话说,Promise是Thenable。但是在某些情况下,我们可能希望实现特定的解析语义。 完成和拒绝Promises如果上一节刚刚介绍的几个术语听起来让你困惑,那别担心。从本节开始,我们将看到所有这些Promises术语的应用实践。在这里,我们将展示一些简单的Promise解决和拒绝的示例。 完成Promises解析器是一个函数,顾名思义,它完成了我们的Promise。这不是完成Promise的唯一方法 - 我们将在后面探索更高级的方式。但到目前为止,这种方法是最常见的。它作为第一个参数传递给执行器函数。这意味着执行器可以通过简单地调用解析器直接完成Promise。但这并不怎么实用,不是吗? 更常见的情况是Promise执行器函数设置即将发生的异步操作 - 例如拨打网络电话。然后,在这些异步操作的回调函数中,我们可以完成这个Promise。在我们的代码中传递一个解析函数,刚开始可能感觉有点违反直觉,但是一旦我们开始使用它们就会发现很有意义。 解析器函数是一个相对Promise来说比较难懂的函数。它只能完成一次Promise。我们可以调用解析器很多次,但只在第一次调用会改变Promise的状态。下面是一个图描述了Promise的可能状态;它还显示了状态之间是如何变化的: 现在,我们来看一些Promise代码。在这里,我们将完成一个promise,它会调用then()完成回调函数: //我们的Promise使用的执行器函数。//第一个参数是解析器函数,在1秒后调用完成Promise。function executor(resolve) { setTimeout(resolve, 1000);}//我们Promise的完成回调函数。//这个简单地在我们的执行程序函数运行后,停止那个定时器。function fulfilled() { console.timeEnd('fulfillment');}//创建promise,并立即运行,//然后启动一个定时器来查看调用完成函数需要多长时间。var promise = new Promise(executor);promise.then(fulfilled);console.time('fulfillment');我们可以看到,解析器函数被调用时fulfilled()函数会被调用。执行器实际上并不调用解析器。相反,它将解析器函数传递给另一个异步函数 - setTimeout()。执行器并不是我们试图去弄清楚的异步代码。可以将执行器视为一种协调程序,它编排异步操作并确定何时执行Promise。 前面的示例未解析任何值。当某个操作的调用者需要确认它成功或失败时,这是一个有效的用例。相反,让我们这次尝试解析一个值,如下所示: //我们的Promise使用的执行函数。//创建Promise后,设置延时一秒钟调用"resolve()",//并解析返回一个字符串值 - "done!"。function executor(resolve) { setTimeout(() => { resolve('done!'); }, 1000);}//我们Promise的完成回调接受一个值参数。//这个值将传递到解析器。function fulfilled(value) { console.log('resolved', value);}//创建我们的Promise,提供执行程序和完成回调函数。var promise = new Promise(executor);promise.then(fulfilled);我们可以看到这段代码与前面的例子非常相似。区别在于我们的解析器函数实际上是在传递给setTimeout()的回调函数的闭包内调用的。这是因为我们正在解析一个字符串值。还有一个将被解析的参数值传递给我们的fulfilled()函数。 ...

October 17, 2019 · 5 min · jiezi

Promise的坑

这不是一篇介绍Promise的文章,如果你还不太了解Promise,可以先看下我之前的关于Promise以及你可能不知道的6件事,觉得写得还可以的希望能动动小手点个赞,谢谢啦(*^▽^*)Lesson One: APromise is a Promise.不行!說的是一輩子!差一年、一個月、一天、一個時辰...都不算一輩子!-- 程蝶衣承诺(Promise)始终应该是承诺(Promise),即使落空,也应该是一个失败(Rejected)的承诺(Promise); Promise 对象渐渐成为了现代 JavaScript 程序异步接口的标准返回。Promise 相对于 Callback,拥有两个先天的优势: Promise 的值在确定后是不可变的。Promise 确保结果一定是异步的,不会出现 releaseZalgo 的问题。If you have an APIwhich takes a callback,and sometimes that callback is called immediately,and other times that callback is called at some point in the future,then you will render any code using this API impossible to reason about, andcause the release of Zalgo.我们重点来看第二点,同样也是Callback的一个重大缺点,就是结果太不可控了,除非我们百分之百确定这个接口是异步的,否则有可能出现上文所说的情况,这个接口一会儿是异步的(第一次网络请求),一会儿是同步的(直接返回本地Cache),而且更糟糕的是,如果这个作者仇视社会的话,没准还会调用好几次回调,而这些都是你没法控制的(┑( ̄ ̄)┍摊手)。而这些 Callback 的缺点同样是 Promise 的卖点,但你以为用了 Promise 就大功告成了嘛: No! ...

October 16, 2019 · 7 min · jiezi

ajax与Promise的那些事儿

/** * 简单的异步操作 */ function callback() { console.log(`我是一个回调函数`); } console.log(`异步之前`); setTimeout(callback, 1000); console.log(`异步之后`); /** * ajax就是典型的异步操作 */ // 发起ajax的过程 const XHR = new XMLHttpRequest(); console.log(XHR); XHR.onreadystatechange = function() { if (XHR.readyState === 4) { if (XHR.status === 200) { console.log(XHR.responseText); } else { console.log(XHR.status); } } }; XHR.open('GET', `https://jsonplaceholder.typicode.com/users`, true); XHR.send(); /** * jQuery请求 */ $.ajax({ type: 'get', url: `https://jsonplaceholder.typicode.com/users`, dataType: 'json', success: function(res) { console.log(res); }, error: function(err) { console.log(err); } }); /** * 用Promise简单的封装一个AJAX函数 */ function myAjax(method, url, params) { return new Promise((resolve, reject) => { const XHR = new XMLHttpRequest(); XHR.onreadystatechange = function() { if (XHR.readyState === 4) { if (XHR.status === 200) { console.log(XHR.responseText); } else { console.log(XHR.status); } } }; // get if (method === 'get' || method === 'GET') { if (typeof params === 'object') { // params拆解成字符串 params = Object.keys(params) .map(function(key) { return ( encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) ); }) .join('&'); } url = params ? url + '?' + params : url; XHR.open(method, url, true); XHR.send(); } //post if (method === 'post' || method === 'POST') { XHR.open(method, url, true); XHR.setRequestHeader( 'Content-type', 'application/json; charset=utf-8' ); XHR.send(JSON.stringify(params)); } }); } /** * 使用定时器来模拟异步操作 * */ let p = new Promise(function(resolve, reject) { console.log(resolve); // 成功之后的回调 console.log(reject); // 失败时候的回调 setTimeout(() => { resolve(100); }, 1000); }); p.then(function(data) { console.log(data); });

October 15, 2019 · 2 min · jiezi

前端面试每日-31-第176天

今天的知识点 (2019.10.09) —— 第176天[html] 做好的页面都有在哪些浏览器上测过[css] 你是怎样对css文件进行压缩合并的?[js] 请用js实现一个promise的方法[软技能] 在工作中,你觉得如何提高幸福指数呢?《论语》,曾子曰:“吾日三省吾身”(我每天多次反省自己)。 前端面试每日3+1题,以面试题来驱动学习,每天进步一点! 让努力成为一种习惯,让奋斗成为一种享受!相信 坚持 的力量!!!欢迎在 Issues 和朋友们一同讨论学习! 项目地址:前端面试每日3+1 【推荐】欢迎跟 jsliang 一起折腾前端,系统整理前端知识,目前正在折腾 LeetCode,打算打通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢迎大家前来讨论,如果觉得对你的学习有一定的帮助,欢迎点个Star, 同时欢迎微信扫码关注 前端剑解 公众号,并加入 “前端学习每日3+1” 微信群相互交流(点击公众号的菜单:进群交流)。 学习不打烊,充电加油只为遇到更好的自己,365天无节假日,每天早上5点纯手工发布面试题(死磕自己,愉悦大家)。希望大家在这浮夸的前端圈里,保持冷静,坚持每天花20分钟来学习与思考。在这千变万化,类库层出不穷的前端,建议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢迎大家到Issues交流,鼓励PR,感谢Star,大家有啥好的建议可以加我微信一起交流讨论!希望大家每日去学习与思考,这才达到来这里的目的!!!(不要为了谁而来,要为自己而来!)交流讨论欢迎大家前来讨论,如果觉得对你的学习有一定的帮助,欢迎点个[Star] https://github.com/haizlin/fe...

October 9, 2019 · 1 min · jiezi

前端面试每日-31-第175天

今天的知识点 (2019.10.08) —— 第175天[html] HTML5的哪些新特性是令你最兴奋的?[css] 如果css文件过大时,如何异步加载它?[js] 请说说你对promise的理解[软技能] 你对“技术服务于生活”的理解是什么?《论语》,曾子曰:“吾日三省吾身”(我每天多次反省自己)。 前端面试每日3+1题,以面试题来驱动学习,每天进步一点! 让努力成为一种习惯,让奋斗成为一种享受!相信 坚持 的力量!!!欢迎在 Issues 和朋友们一同讨论学习! 项目地址:前端面试每日3+1 【推荐】欢迎跟 jsliang 一起折腾前端,系统整理前端知识,目前正在折腾 LeetCode,打算打通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢迎大家前来讨论,如果觉得对你的学习有一定的帮助,欢迎点个Star, 同时欢迎微信扫码关注 前端剑解 公众号,并加入 “前端学习每日3+1” 微信群相互交流(点击公众号的菜单:进群交流)。 学习不打烊,充电加油只为遇到更好的自己,365天无节假日,每天早上5点纯手工发布面试题(死磕自己,愉悦大家)。希望大家在这浮夸的前端圈里,保持冷静,坚持每天花20分钟来学习与思考。在这千变万化,类库层出不穷的前端,建议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢迎大家到Issues交流,鼓励PR,感谢Star,大家有啥好的建议可以加我微信一起交流讨论!希望大家每日去学习与思考,这才达到来这里的目的!!!(不要为了谁而来,要为自己而来!)交流讨论欢迎大家前来讨论,如果觉得对你的学习有一定的帮助,欢迎点个[Star] https://github.com/haizlin/fe...

October 8, 2019 · 1 min · jiezi

前端自动化测试二

上一篇文章,我们已经讲述了Jest中的基本使用,这一篇我们来说说如何深度使用Jest 在测试中我们会遇到很多问题,像如何测试异步逻辑,如何mock接口数据等... 通过这一篇文章,可以让你在开发中对Jest的应用游刃有余,让我们逐一击破各个疑惑吧! 1.Jest进阶使用1.1 异步函数的测试提到异步无非就两种情况,一种是回调函数的方式,另一种就是现在流行的promise方式 export const getDataThroughCallback = fn => { setTimeout(() => { fn({ name: "webyouxuan" }); }, 1000);};export const getDataThroughPromise = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ name: "webyouxuan" }); }, 1000); });};我们来编写async.test.js方法 import {getDataThroughCallback,getDataThroughPromise} from './3.getData';// 默认测试用例不会等待测试完成,所以增加done参数,当完成时调用done函数it('测试传入回调函数 获取异步返回结果',(done)=>{ // 异步测试方法可以通过done getDataThroughCallback((data)=>{ expect(data).toEqual({ name:'webyouxuan' }); done(); })})// 返回一个promise 会等待这个promise执行完成it('测试promise 返回结果 1',()=>{ return getDataThroughPromise().then(data=>{ expect(data).toEqual({ name:'webyouxuan' }); })})// 直接使用async + await语法it('测试promise 返回结果 2',async ()=>{ let data = await getDataThroughPromise(); expect(data).toEqual({ name:'webyouxuan' });})// 使用自带匹配器it('测试promise 返回结果 3',async ()=>{ expect(getDataThroughPromise()).resolves.toMatchObject({ name:'webyouxuan' })})2.Jest中的mock2.1 模拟函数jest.fn()为什么要模拟函数呢?来看下面这种场景,你要如何测试 ...

September 10, 2019 · 3 min · jiezi

使用Promise封装Websocket

await 后面若是跟的Promise,则接受Promise resolve的值。Promise reject的值需要try...catch住,或者await 后面的表达式跟着.catch()//私有变量const options = Symbol("options");const intUniqueId = Symbol("intUniqueId");//const arrQueueRequest = Symbol("arrQueueRequest");const arrQueueNotice = Symbol("arrQueueNotice");export default class SocketConn { /** * 构造函数,初始化数据 * @param {*} objOption */ constructor(objOption) { this[options] = { connWbSK: null, //Websocket实例 secure: objOption.secure || "wss", hostName: objOption.hostName || "127.0.0.1", portNo: objOption.portNo || "80", user: objOption.user, password: objOption.password }; this[intUniqueId] = 0; //请求的id,也是请求的key值(在请求序列里根据id找对应的包) this[arrQueueRequest] = {};//请求序列 this[arrQueueNotice] = {};//server的通知序列 }; /***************私有函数start*********** */ /** * 处理请求对应的响应 */ _onRequestMessage = (objResp) => { let {id} = objResp;//从响应里获取id this[arrQueueRequest][id].resolve(objResp);//只返回resolve函数,不reject,可以在外面捕获 delete this[arrQueueRequest][id];//删除序列id对应数据包 }; _onNoticeMessage = (objResp) => { //处理通知 } /***************私有函数end*********** */ /***************公开函数start*********** */ //公开函数 open = (onSocketClose) => { //await只能接收resolve的值,reject的值需要在外面catch return new Promise((resolve, reject) => { let _private = this[options], url = "" + _private.secure + "://" + _private.hostName; if (_private.portNo) { url += ":" + _private.portNo; } url += "?"; url += "user=" + _private.user; url += "&password=" + _private.password; _private.connWbSK = new WebSocket(url); _private.connWbSK.onopen = (event) => {//成功连接上 console.log("onopen") resolve(true); } _private.connWbSK.onmessage = (event) => {//收到server发来的消息 let objResp = JSON.parse(event.data); //如果是我们发送的请求 if(objResp.packet_type === "response"){ this._onRequestMessage(objResp); }else{//如果是server的通知 this._onNoticeMessage(objResp); } } _private.connWbSK.onerror = (event) => { // reject(false); }; //传入onclose事件,以便于server主动断开时触发 _private.connWbSK.onclose = (event) => { onSocketClose(event); }; }); }; /** * 外部调用的发送方法 */ send = (serviceName, methodName, objData, dataFormat, timeout) => { return new Promise((resolve, reject) => { let objPkg, objPkgSendReq; this[intUniqueId] += 1; //构建发送包 objData = JSON.stringify(objData);//先序列化 objData = Base64.encode(objData);//加密 objPkg = { id: this[intUniqueId], packet_type: "request", service: serviceName, method: methodName, data: objData }; objPkgSendReq = JSON.stringify(objPkg); this[arrQueueRequest][objPkg.id] = { id: objPkg.id, strReq: objPkgSendReq, reject: reject, resolve: resolve, dataFormat: dataFormat, timeoutId: null } this[options].connWbSK.send(this[arrQueueRequest][objPkg.id].strReq); }) }; //主动关闭连接,并且重置数据 closeSocketAndResetVar = () => { if(this[options].connWbSK){ this[options].connWbSK.close(); this[options].connWbSK = null; } }; /***************公开函数end*********** */}使用 ...

September 9, 2019 · 2 min · jiezi

asyncawait-和-Promise-的用例关系

假设我们有三个请求,req1,req2, req3,三个请求后者依赖前者的请求结果。我们先使用Promise封装一个异步请求的方法。 Promise 异步请求使用Promise可以非常容易的封装一个异步处理的业务,通过reslove/reject两个callback来返回执行结果。 我们使用 Promise 封装一个 http get方法。 // 返回一个 Promise 对象(PromiseStatus: pending)function asyncHttpGet(url) { return new Promise((resolve, reject) => { const request = new Request(url, {method: 'GET'}) // 使用 fetch 请求 fetch(request) .then(response => { if (200 == response.status) { return response.text() } else { // goto catch throw new Error("request failed: " + response.status) } }).then(html => { // console.log(html) resolve(url + " get success") }).catch(err => { reject(err) }); })}fetch返回的其实就是一个Promise对象,上例是想演示下resolve/reject的使用上下文,如果你早已get,下面给出直接使用fetch的方式: ...

July 16, 2019 · 4 min · jiezi

ES6-Promise

参考https://pouchdb.com/2015/05/1...https://developer.mozilla.org...http://es6.ruanyifeng.com/#do... Promises是一种编写异步代码的方法。Promise 对象用于表示一个异步操作的最终状态(完成或失败),以及该异步操作的结果值。Promise 使用Promise适用于这样的场景,后面的操作必须根据前面的操作的结果做出相应的反应。 那么后面的操作必须等前面的操作完成并且获得前面操作的结果。假设我们现在有三个操作doSomethingFirst,doSomethingSecond 和finalHandler。doSomethingSecond 需要根据 doSomethingFirst 的结果做出反应finalHandler 需要根据 doSomethingSecond 的结果做出反应流程如下://resOfDoSomethingFirst 是 DoSomethingFirst的结果 doSomethingFirst|-----------------| doSomethingSecond(resOfDoSomethingFirst) |------------------| finalHandler(doSomethingSecond的结果) |------------------|实现这个场景需要解决以下两个问题: 后面的操作 如何知道 前面的操作 完成了 后面的操作 如何知道 前面的操作 的执行结果是什么//包装第一个操作在 doSomethingFirst 中const doSomethingFirst = new Promise(function(resolve, reject) { // ... some code if (/*操作成功 */){ resolve(doSomethingFirstValue); //将doSomethingFirst对象的状态从“pending”变为“resolved” } else { reject(doSomethingFirstError); }});//包装第二个操作在 doSdoSomethingSecond(resOfDoSomethingFirst) { // ... some code return somePromise(); //返回一个promise 对象});//整个操作流程如下:doSomethingFirst() .then(doSdoSomethingSecond) .then(finalHandler) .catch(function (err) { console.log(err); }) .finally(() => {···}); 通过new Promise创建的Promise 实例doSomethingFirst 有以下方法: Promise.all(iterable) Promise.race(iterable) Promise.reject(reason) Promise.resolve(value) Promise.prototype.catch(onRejected) Promise.prototype.then(onFulfilled, onRejected) Promise.prototype.finally(onFinally)1. doSomethingSecond 如何知道 doSomethingFirst 操作完成了 通过doSomethingFirst状态的变更通知。 一个 Promise有以下几种状态: pending: 操作未完成。 fulfilled: 操作完成,并且成功。 rejected: 操作完成,但是失败。 resolve()函数: 在异步操作成功时调用 作用是:将Promise对象的状态从 pending 变为 fulfilled,并将异步操作的结果,作为参数传递出去; reject()函数: 在异步操作失败时调用 作用是:将Promise对象的状态从 pending 变为 rejected, 并将异步操作的错误,作为参数传递出去。 当操作完成时(Promise对象的状态变为fulfilled 或 rejected时),doSomethingFirst 就会通过then()函数调用doSomethingSecond,doSomethingSecond就知道doSomethingFirst已经完成了。2. doSomethingSecond 如何知道 doSomethingFirst 的执行结果是什么 doSomethingFirst通过给then()函数调用doSomethingSecond(resOfDoSomethingFirst)并把执行结果resOfDoSomethingFirst作为参数传递给doSomethingSecondPromise.prototype.then(onFulfilled, onRejected)用于为 Promise 实例添加状态改变时的回调函数返回值:一个新的 Promise,所可以采用链式写法,then()函数后面再调用then()函数参数: onFulfilled 是一个函数,有一个参数,用来记录变成fulfilled状态返回的结果 当doSomethingFirst这个Promise的状态变成fulfilled 时,onFulfilled作为回调函数被调用 onRejected 是一个函数,有一个参数,用来记录变成rejected状态返回的原因 当doSomethingFirst这个Promise的状态变成rejected 时,onRejected作为回调函数被调用 在当前的例子里参数onFulfilled就是doSomethingSecond(resOfDoSomethingFirst),resOfDoSomethingFirst 记录了doSomethingFirst 变成fulfilled状态返回的结果。注意事项: 当给then()传入的参数不是函数时,它实际上将其解释为then(null),将使先前的Promise的结果落空 Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) { console.log(result); // foo }); 等价于 Promise.resolve('foo').then(null).then(function (result) { console.log(result);// foo }); 上面的代码并没有按照我们期望的打印"bar", 而是打印"foo" 正确的写法是: Promise.resolve('foo').then(function () { return Promise.resolve('bar'); }).then(function (result) { console.log(result); // bar });给then()传入的参数是函数时,函数的内部我们可以做以下三件事: 1. 返回另外一个promise 2. 返回一个同步值 3. 抛出一个同步错误 示例:返回另外一个promise doSomethingFirst() .then(doSdoSomethingSecond) .then(finalHandler) //somePromise() 返回promise对象 function doSomethingSecond(resOfDoSomethingFirst) { return somePromise(); //有return, finalHandler接受到的是resOfDoSomethingSecond //somePromise();// 没有return, finalHandler接受到的是undefined } function finalHandler(resOfDoSomethingSecond) { // handle resOfDoSomethingSecond } 示例:返回同步值 && 抛出一个同步错误 返回同步值实际上是将同步代码转换为Promisey代码的一种很棒的方法。例如,假设我们有一个用户的内存缓存。我们可以做到: //getUserByName 和 getUserAccountById 都返回promise对象 getUserByName('nolan').then(function (user) { if (user.isLoggedOut()) { //如果用户注销 throw new Error('user logged out!'); // 抛出一个同步错误 } if (inMemoryCache[user.id]) { return inMemoryCache[user.id]; // 返回一个同步值! } return getUserAccountById(user.id); // 返回一个promise! }).then(function (userAccount) { // I got a user account! }).catch(function (err) { // Boo, I got an error! }); 如果用户注销,catch()将收到一个同步错误;通过callbacks,这个错误会被忽略 如果任何promise被拒绝,catch()将收到一个异步错误。通常情况下,一个promise 依赖于另一个promise ,但当我们需要两个promises的输出。我们该怎么做 getUserByName('nolan').then(function (user) { return getUserAccountById(user.id); }).then(function (userAccount) { // 在这里我们已经获取到了用户账号userAccount, // 但是我们也需要user对象时该怎么做? }); 解决方案: function onGetUserAndUserAccount(user, userAccount) { return doSomething(user, userAccount); } function onGetUser(user) { return getUserAccountById(user.id).then(function (userAccount) { return onGetUserAndUserAccount(user, userAccount); }); } getUserByName('nolan') .then(onGetUser) .then(function () { // at this point, doSomething() is done, and we are back to indentation 0 });Promise.prototype.catch(onRejected)用于指定发生错误时的回调函数。catch 可以捕获返回值:返回一个Promise,处理状态变为rejected的情况参数: onRejected 是一个函数,有一个参数,用来记录变成rejected状态返回的原因。 当promise 状态变为rejected时被调用。注意事项:catch(rejectHandler) 等同于.then(null, rejectHandler)或.then(undefined, rejectHandler)但是 then(resolveHandler).catch(rejectHandler) 和then(resolveHandler, rejectHandler)不是完全相同的。区别在于:当使用then(resolveHandler, rejectHandler)格式时,如果resolveHandler本身抛出了错误,那么rejecthandler实际上不会捕获错误。所以更建议使用catch 而不是then的第二个参数。示例: var p1 = new Promise((resolve, reject) => { resolve('one'); }); // catch函数中可以捕捉到resolveHandler 中的error并打印 p1.then(function () { throw new Error('oh noes'); }).catch(function (err) { console.log('err=', err); // Error: oh noes }); // reject函数不能捕捉到resolveHandler中的error p1.then(function () { throw new Error('oh noes'); }, function (err) { console.log('err=', err); });Promise.prototype.finally(onFinally)用于指定不管 Promise 对象最后状态如何,都会执行的操作finally本质上是then方法的特例promise.finally(() => { // 语句});// 等同于promise.then( result => { // 语句 return result; }, error => { // 语句 throw error; });返回值:返回一个Promise,这个Promise对象设置了 finally 回调函数参数:Promise 结束后调用的函数,onFinally 这个函数不接收任何参数,它仅用于无论最终结果如何都要执行的情况。所以finally方法里面的操作,不应依赖于 Promise 的执行结果。示例: promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});Promise.resolve(value)用于将现有对象转为 Promise 对象 new Promise(function (resolve, reject) { resolve(someSynchronousValue); }).then(/* ... */); 等价于 Promise.resolve(someSynchronousValue).then(/* ... */);返回值:返回一个Promise 对象,这个Promise对象是被给定的值解析过的。参数: value 将被Promise对象解析的参数 参数类型: Promise对象 具有then方法的对象 没有then方法的对象 不带有任何参数示例1:参数是一个Promise对象 Promise.resolve将不做任何修改、原封不动地返回这个Promise对象 var p = Promise.resolve([1,2,3]); console.log("p=", p); //"p=" [object Promise] console.log('p type= ', typeof(p)); // "p type= " "object" p.then(function(v) { console.log("v=",v); //"v=" Array [1, 2, 3] console.log("v type=",typeof(v)); //"v type=" "object" }); var p2 = Promise.resolve(p); console.log("p2=", p2); //"p2=" [object Promise] console.log('p2 type= ', typeof(p2)); //"p2 type= " "object" p2.then(function(v) { console.log("p2 v=",v); //"p2 v=" Array [1, 2, 3] console.log("p2 v type=",typeof(v)); //"p2 v type=" "object" }); p2 == p示例2:参数是一个具有then方法的对象 返回的promise会采用这个thenable的对象的最终状态。 let thenable = { then: function(resolve, reject) { resolve(42); } }; let p = Promise.resolve(thenable); p.then(function(value) { console.log(value); // 42 }); Promise.resolve方法会将thenable对象转为 Promise 对象,然后就立即执行thenable对象的then方法,thenable对象的then方法执行后,对象p的状态就变为resolved,从而立即执行最后那个then方法指定的回调函数,输出 42示例3:参数是一个没有then方法的对象 var p = Promise.resolve([1,2,3]); console.log("p=", p); //"p=" [object Promise] console.log('p type= ', typeof(p)); // "p type= " "object" p.then(function(v) { console.log("v=",v); //"v=" Array [1, 2, 3] console.log("v type=",typeof(v)); //"v type=" "object" }); var p = Promise.resolve(123); console.log("p=", p); //"p=" [object Promise] console.log('p type= ', typeof(p)); // "p type= " "object" p.then(function(v) { console.log("v=",v); //"v=" 123 console.log("v type=",typeof(v)); //"v type=" "number" }); var p = Promise.resolve("123"); console.log("p=", p); //"p=" [object Promise] console.log('p type= ', typeof(p)); // "p type= " "object" p.then(function(v) { console.log("v=",v); //"v=" "123" console.log("v type=",typeof(v)); //"v type=" "string" });示例4:不带有任何参数 var p = Promise.resolve(); console.log("p=", p); //"p=" [object Promise] console.log('p type= ', typeof(p)); // "p type= " "object" p.then(function(v) { console.log("v=",v); //"v=" undefined console.log("v type=",typeof(v)); //"v type=" "undefined" });注意事项: 立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时 //在下一轮“事件循环”开始时执行 setTimeout(function () { console.log('three'); }, 0); //在本轮“事件循环”结束时执行 Promise.resolve().then(function () { console.log('two'); }); //立即执行 console.log('one'); 结果: "one" "two" "three" Promise.reject(reason)返回值:返回一个Promise 对象,这个Promise 对象 带有状态是rejected的原因参数: reason 表示Promise被拒绝的原因 new Promise(function (resolve, reject) { reject(someSynchronousReson); }).then(null,function(reason){ //... });等价于 Promise.reject(someSynchronousReson) .then(null, function(reason) { //... }); 示例: new Promise(function (resolve, reject) { reject("reject reason"); }).then(null,function(reason){ console.log(reason);//"reject reason" }); Promise.reject("reject reason").then(null, function(reason) { console.log(reason); // "reject reason" }); Promise.reject(new Error("reject reason")).then(null, function(error) { console.log(error); // Error: reject reason });注意事项:与Promise.resolve不同的是Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。 const thenable = { then(resolve, reject) { reject('出错了'); } }; Promise.reject(thenable) .catch(e => { //e不是reject抛出的“出错了”这个字符串,而是thenable对象。 console.log(e === thenable) // true })Promise.all(iterable)用于将多个 Promise 实例,包装成一个新的 Promise 实例返回值:返回一个新的promise对象,iterable中所有的promise都变成resolved状态时返回的 promise才会变为resolved状态; iterable中有一个promise变成rejected状态,promise就会变为rejected状态。参数: iterable 一个可迭代对象,eg Array 或 String示例: iterable中所有的promise都变成resolved状态时返回的 promise才会变为resolved状态 var p1 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, 'one'); }); var p2 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'two'); }); var p3 = new Promise((resolve, reject) => { setTimeout(resolve, 3000, 'three'); }); const p = Promise.all([p1, p2, p3]).then(values => { console.log(values); //["one", "two", "three"] }).catch(reason => { console.log(reason); //没执行 }); 流程:p创建时为pending状态,当p1, p2, p3的状态都变成resolved时触发p变成resolved状态。 p1 1s |----------|resolved p2 2s |--------------------|resolved p3 3s |------------------------------|resolved p resolved 示例:iterable中有一个promise变成rejected状态,promise就会变为rejected状态 var p1 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, 'one'); }); var p2 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'two'); }); var p3 = new Promise((resolve, reject) => { reject('reject'); }); const p = Promise.all([p1, p2, p3]).then(values => { console.log(values); //没执行 }).catch(reason => { console.log(reason); //"reject" }); 流程:p创建时为pending状态,p3先执行完,p3的状态变成rejected时触发p变成rejected状态。 p1 1s |----------|resolved p2 2s |--------------------|resolved p3 |-|rejected p rejected 示例:如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法 var p1 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, 'one'); }); var p2 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'two'); }); var p3 = new Promise((resolve, reject) => { reject('reject'); }).catch(reason => { console.log(reason); //reject }); const p = Promise.all([p1, p2, p3]).then(values => { console.log(values); // ["one", "two", undefined] }).catch(reason => { console.log(reason); //没执行 }); 流程:p创建时为pending状态,p3先执行完,p3的状态变成rejected时,调用自己定义的catch函数抛出错误,不会触发p变成rejected状态。当p1 和 p2 也执行完时,触发p变成resolved状态 p1 1s |----------|resolved p2 2s |--------------------|resolved p3 |-|rejected |p resolved示例:如果传入的参数是一个空的可迭代对象,则返回一个resolved状态的 Promise const p = Promise.all([]).then(values => { //p创建时状态就为resolved console.log(values); // [] });Promise.race(iterable)同样是将多个 Promise 实例,包装成一个新的 Promise 实例返回值:返回一个新的promise对象,一旦iterable中的某个promise变为resolved或rejected状态,返回的 promise就会变为resolved或rejected状态。谁最先执行完就返回谁的状态参数: iterable 一个可迭代对象,eg Array 或 String示例: 一旦iterable中的某个promise变为resolved状态,返回的 promise就会变为resolved状态。 var p1 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, 'one'); }); var p2 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'two'); }); var p3 = new Promise((resolve, reject) => { setTimeout(resolve, 3000, 'three'); }); const p = Promise.race([p1, p2, p3]).then(values => { console.log(values); //["one", "two", "three"] }).catch(reason => { console.log(reason); //没执行 });流程:p创建时为pending状态,p1先执行完,p1的状态都变成resolved时触发p变成resolved状态。p1 1s|----------|resolved p resolved p2 2s |--------------------|resolvedp3 3s |------------------------------|resolved 示例:一旦iterable中的某个promise变为rejected状态,返回的 promise就会变为rejected状态。var p1 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, 'one'); }); var p2 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'two'); });var p3 = new Promise((resolve, reject) => { reject('reject');});const p = Promise.all([p1, p2, p3]).then(values => { console.log(values); //没执行}).catch(reason => { console.log(reason); //"reject"}); 流程:p创建时为pending状态,p3先执行完,p3的状态变成rejected时触发p变成rejected状态。p1 1s|----------|resolved p2 2s |--------------------|resolvedp3|-|rejected p rejected

July 14, 2019 · 6 min · jiezi

深入理解asyncawait来处理异步

目前async/await 已经被标准化,我们需要尽快将学习进程提上日程。先说一下async的用法,它作为一个关键字放到函数前面,用于表示函数是一个异步函数,因为async就是异步的意思,异步函数也就意味着该函数的执行不会阻塞后面代码的执行。下面写一个async 函数: async function timeout() { return 'hello world';}语法很简单,就是在函数前面加上async 关键字,来表示它是异步的,那怎么调用呢?async 函数也是函数,平时我们怎么使用函数就怎么使用它,直接加括号调用就可以了,为了表示它没有阻塞它后面代码的执行,我们在async 函数调用之后加一句console.log。 async function timeout() { return 'hello world'}timeout();console.log('虽然在后面,但是我先执行');async 函数 timeout 调用了,但是没有任何输出,它不是应该返回 'hello world',先不要着急, 看一看timeout()执行返回了什么? 把上面的 timeout() 语句改为console.log(timeout())。 async function timeout() { return 'hello world'}console.log(timeout());console.log('虽然在后面,但是我先执行');继续看控制台原来async 函数返回的是一个promise 对象,如果要获取到promise 返回值,我们应该用then 方法, 继续修改代码。 async function timeout() { return 'hello world'}timeout().then(result => { console.log(result);})console.log('虽然在后面,但是我先执行');看控制台我们获取到了"hello world',同时timeout 的执行也没有阻塞后面代码的执行,和我们刚才说的一致。这时,你可能注意到控制台中的Promise 有一个resolved,这是async 函数内部的实现原理。如果async 函数中有返回一个值,当调用该函数时,内部会调用Promise.solve() 方法把它转化成一个promise 对象作为返回,但如果timeout 函数内部抛出错误呢? 那么就会调用Promise.reject() 返回一个promise 对象,这时修改一下timeout 函数。 async function timeout(flag) { if (flag) { return 'hello world' } else { throw 'my god, failure' }}console.log(timeout(true)) // 调用Promise.resolve() 返回promise 对象。console.log(timeout(false)); // 调用Promise.reject() 返回promise 对象。如果函数内部抛出错误,promise 对象有一个catch 方法进行捕获。 ...

July 12, 2019 · 1 min · jiezi

一道常见的面试题

面试题题目页面上有一个输入框,两个按钮,A按钮和B按钮,点击A或者B分别会发送一个异步请求,请求完成后,结果会显示在输入框中。题目要求,用户随机点击A和B多次,要求输入框显示结果时,按照用户点击的顺序显示,举例: 用户点击了一次A,然后点击一次B,又点击一次A,输入框显示结果的顺序为先显示A异步请求结果,再次显示B的请求结果,最后再次显示A的请求结果。 思考当时被问到的时候,几个想法 写个高阶函数,处理异步请求怎么保证顺序,我是想将其放入数组中,然后按顺序执行当时也没写出来,但觉得很有意思。回去思考实践来一下代码: /** * A的请求;pA(promise对象) * B的请求;pA(promise对象) * @click 事件函数 handler */let arr = []let itfunction* main() { //进来的是pA,pB封装后的方法 const data = yield arr.unshift()() if(arr.length > 0) { it = main() it.next() }}it = main()//封装pA,pB的请求function pn(promise) { return promise.then(res => it.next(res))}//当点击按钮发送请求时,将相应的请求加入数组中function handler(pn) { arr.push(pn) //数组不为空说明请求触发中 if(arr.length = 0) { it.next() }}说明 点击按钮时,先发放入请求;数组为空说明,还没执行;就先执行;不为空,则加入数组中,等待执行利用迭代器,遍历数组,执行请求封装函数fn,思路来自之前看的Generator的异步请求方式总结以上是我自己思路,觉得蛮有意思的。写下来记录一下

July 9, 2019 · 1 min · jiezi

翻译We-have-a-problem-with-promises

原文:https://pouchdb.com/2015/05/1... JavaScripts的朋友们,是时候承认了: we have a problem with promises。不,不是promises本身。正如A+ spec所定义的,promises是非常棒的。 在过去的一年里,当我看到许多程序员在PouchDB API和其他promise-heavy APIs上挣扎时,我发现了一个大问题:我们中的许多人使用promises 时没有真正理解它们。 如果你觉得很难相信,想想我最近在Twitter上发布的这个谜题: Q: What is the difference between these four promises? doSomething().then(function () { return doSomethingElse();});doSomething().then(function () { doSomethingElse();});doSomething().then(doSomethingElse());doSomething().then(doSomethingElse);如果你知道答案,那么恭喜你:你是一个承诺忍者。我允许您停止阅读此日志。对于其他99.99%的人来说,你是一个很好的同伴。没有人回应我的推特,也没有人能解决这个问题,我自己对#3的答案感到惊讶。是的,即使我写了测验!答案在这篇文章的最后,但首先,我想探讨一下为什么promises一开始就那么棘手,为什么我们中的许多人——新手和专家——会被promises绊倒。我还将提供我认为是独特见解的东西,一个奇异的把戏,它使promises很容易理解。是的,我真的相信在那之后他们不会那么难!但首先,让我们挑战一些关于promises的常见假设。Wherefore promises?如果你读过有关promises的文献,你会经常发现对the pyramid of doom(https://medium.com/@wavded/managing-node-js-callback-hell-1fe03ba8baf)的引用,其中有一些可怕的callback-y代码稳步地向屏幕的右侧延伸。promises确实解决了这个问题,但它不仅仅是缩进。正如"Redemption from Callback Hell"(http://youtu.be/hf1T_AONQJU)中所解释的,callbacks的真正问题是它们剥夺了我们return和throw这样的关键字。相反,我们的程序的整个流程基于side effects:一个函数偶然调用另一个函数。事实上,callbacks 做了一些更险恶的事情:它们剥夺了我们的stack, stack在编程语言中我们通常认为是理所当然的。写没有stack的代码很像驾驶一辆没有刹车踏板的汽车:你不会意识到你有多么需要它,直到你伸手去拿它而它不在那里。promises的全部要点是就是把异步时丢失的语言基础还给我们:return, throw, 和 stack。但是你必须知道如何正确地使用promises,才能利用它们。Rookie mistakes有些人试图把承诺解释成cartoon(https://www.andyshora.com/promises-angularjs-explained-as-cartoon.html),或者以一种非常面向名词的方式:“哦,正是你可以传递的东西代表了一个异步值。”我觉得这样的解释没什么帮助。对我来说,promises都是关于代码结构和流程的。所以我认为最好是回顾一些常见的错误,并展示如何修复它们。我把这些叫做"rookie mistakes",意思是,“你现在是新手了,孩子,但你很快就会成为职业选手。”Quick digression::“promises”对不同的人来说意味着很多不同的事情,但是在本文中,我将只讨论官方规范(https://promisesaplus.com/),就像window.Promise在现代浏览器中一样。并不是所有的浏览器都有window.Promise,因此,要想得到一个好的polyfill,请看一个名为Lie(https://github.com/calvinmetcalf/lie)的库,它是目前最小的符合规范的库。Rookie mistake #1: the promisey pyramid of doom看看人们是如何使用PouchDB的,PouchDB有一个很大程度上基于promise的API,我发现很多糟糕的promise模式。最常见的糟糕的做法是:remotedb.allDocs({ include_docs: true, attachments: true}).then(function (result) { var docs = result.rows; docs.forEach(function(element) { localdb.put(element.doc).then(function(response) { alert("Pulled doc with id " + element.doc._id + " and added to local db."); }).catch(function (err) { if (err.name == 'conflict') { localdb.get(element.doc._id).then(function (resp) { localdb.remove(resp._id, resp._rev).then(function (resp) {// et cetera...是的,事实证明你可以像回调一样使用promises ,是的,这很像用电动砂光机锉指甲,但你可以做到。如果你认为这类错误仅仅局限于绝对初学者,你会惊讶地发现我确实从官方的黑莓开发者博客中获取了上述代码!旧的回调习惯很难改变。(对开发人员说:很抱歉挑你的毛病,但你的例子很有启发性。)A better style is this one:remotedb.allDocs(...).then(function (resultOfAllDocs) { return localdb.put(...);}).then(function (resultOfPut) { return localdb.get(...);}).then(function (resultOfGet) { return localdb.put(...);}).catch(function (err) { console.log(err);});这被称为composing promises,它是promises的great superpowers之一。每个函数只有在上一个Promise resolved后才会被调用,并且将使用该Promise的输出来调用它。更多的内容以后再谈。Rookie mistake #2: WTF, how do I use forEach() with promises?这就是大多数人对承诺的理解开始崩溃的地方。一旦他们到了熟悉的foreach()循环(或者for循环,或者while循环),他们就不知道如何让它与promises一起工作。所以他们写了这样的东西: // I want to remove() all docs db.allDocs({include_docs: true}).then(function (result) { result.rows.forEach(function (row) { db.remove(row.doc); }); }).then(function () { // I naively believe all docs have been removed() now! });这个代码有什么问题?问题是第一个函数实际上返回undefined,这意味着第二个函数不等待对所有文档调用db.remove()。实际上,它不需要等待任何东西,并且可以在删除任意数量的文档后执行!这是一个特别阴险的bug,因为您可能不会注意到任何错误,假设PouchDB删除这些文档的速度足以更新您的UI。这个bug可能只在odd race条件下出现,或者在某些浏览器中出现,此时几乎不可能进行调试。所有这些的TLDR 都是forEach()/for/while 不是您要查找的构造。你需要Promise.all(): db.allDocs({include_docs: true}).then(function (result) { return Promise.all(result.rows.map(function (row) { return db.remove(row.doc); })); }).then(function (arrayOfResults) { // All docs have really been removed() now! });这是怎么回事?基本上Promise.all() 接受一个array of promises作为输入,然后它给您另一个promise,该promise只在其他所有的promise都resolved时才会解决。它是for循环的异步等价物。Promise.all() 还将一个结果数组传递给下一个函数,这非常有用,例如,如果您试图从pouchdb去get()多个结果。如果它的任何一个sub-promises are rejected,那么all()承诺也会被拒绝,这更有用。Rookie mistake #3: forgetting to add .catch()这是另一个常见的错误。幸运的是,他们的promises永远不会抛出错误,许多开发人员忘记在代码中的所有地方添加.catch()。不幸的是,这意味着任何抛出的错误都将被吞没,您甚至不会在控制台中看到它们。这可能是调试真正的苦恼。为了避免这种糟糕的情况,我养成了在我的promise chains中添加以下代码的习惯: somePromise().then(function () { return anotherPromise(); }).then(function () { return yetAnotherPromise(); }).catch(console.log.bind(console)); // <-- this is badass即使您不期望出现错误,也要谨慎地添加catch()。如果你的假设被证明是错误的,这会让你的生活更轻松。Rookie mistake #4: using "deferred"这是一个错误 我看all the time,我甚至不愿意在这里重复它,因为我担心,像甲虫汁一样,仅仅调用它的名字就会引发更多的例子。简言之,promises 有着悠久的历史,而JavaScript社区花了很长时间才使其正确。早期,jQuery 和Angular在各地都使用这种“deferred”模式,现在已经被ES6 Promise规范所取代,由“good”库(如Q, When, RSVP, Bluebird, Lie, and others库)实现。所以如果你在代码中写这个词(我不会第三次重复!)你做错了一些事。下面是如何避免它。首先,大多数承诺库都为您提供了从第三方库“import”promises 的方法。例如,Angular的$q模块允许您使用$q.when()包装non-$q承诺。所以Angular用户可以这样包装PouchDB承诺: $q.when(db.put(doc)).then(/* ... */); // <-- this is all the code you need另一种策略是使用revealing constructor pattern(https://blog.domenic.me/the-revealing-constructor-pattern/),这对于包装 non-promise的API很有用。例如,要包装基于回调的API,如Node的fs.readfile(),只需执行以下操作: new Promise(function (resolve, reject) { fs.readFile('myfile.txt', function (err, file) { if (err) { return reject(err); } resolve(file); }); }).then(/* ... */)Done! We have defeated the dreaded def... Aha, caught myself. :)有关为什么这是anti-pattern的更多信息,请访问Bluebird wiki上的Promise anti-patterns页面(https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns#the-deferred-anti-pattern)。Rookie mistake #5: using side effects instead of returning这个代码怎么了? somePromise().then(function () { someOtherPromise(); }).then(function () { // Gee, I hope someOtherPromise() has resolved! // Spoiler alert: it hasn't. });好吧,这是一个很好的观点,可以谈论关于promises的所有你需要知道的事情。说真的,这是一个one weird trick,一旦你理解了它,就会阻止我所说的所有错误。准备好了吗?正如我之前所说,promises 的魔力在于,它们把我们宝贵的return 和 throw还给我们。但在实践中这到底是什么样子的呢?每一个承诺都会给你一个then()方法(或catch(),它只是then(null, ...)的语法糖)。这里是then()函数的内部: somePromise().then(function () { // I'm inside a then() function! });我们在这里能做什么?有三件事: 1. return another promise 2. return a synchronous value (or undefined) 3. throw a synchronous error就这样。一旦你理解了这个诀窍,你就明白了promises。所以,So let's go through each point one at a time.。1. Return another promise 这是您在promise文献中看到的常见模式,如上面的“composing promises”示例所示: getUserByName('nolan').then(function (user) { return getUserAccountById(user.id); }).then(function (userAccount) { // I got a user account! }); 请注意,我正在返回第二个promise—return是至关重要的。如果我没有说return,那么getUserAccountByID()实际上是一个side effect,下一个函数将接收undefined而不是userAccount。2. Return a synchronous value (or undefined)返回undefined通常是一个错误,但返回同步值实际上是将同步代码转换为Promisey代码的一种很棒的方法。例如,假设我们有一个用户的内存缓存。我们可以做到: getUserByName('nolan').then(function (user) { if (inMemoryCache[user.id]) { return inMemoryCache[user.id]; // returning a synchronous value! } return getUserAccountById(user.id); // returning a promise! }).then(function (userAccount) { // I got a user account! });那不是太棒了吗?第二个函数不关心是同步还是异步获取用户帐户,第一个函数可以自由返回同步或异步值。不幸的是,在JavaScript中,non-returning函数在技术上返回undefined结果是不方便的,这意味着当您打算返回某些内容时,很容易意外地引入side effects 。出于这个原因,我习惯于总是从then()函数内部返回或抛出。我建议你也这么做。Throw a synchronous error说到throw,这就是promises可以变得令人惊叹的地方。假设我们想要抛出一个同步错误,以防用户注销。这很容易: ...

July 6, 2019 · 5 min · jiezi

JavaScript异步编程

前言从我们一开始学习JavaScript的时候就听到过一段话:JS是单线程的,天生异步,适合IO密集型,不适合CPU密集型。但是,多数JavaScript开发者从来没有认真思考过自己程序中的异步到底是怎么出现的,以及为什么会出现,也没有探索过处理异步的其他方法。到目前为止,还有很多人坚持认为回调函数就完全够用了。 但是,随着JavaScript面临的需求越来越多,它可以运行在浏览器、服务器、甚至是嵌入式设备上,为了满足这些需求,JavaScript的规模和复杂性也在持续增长,使用回调函数来管理异步也越来越让人痛苦,这一切,都需要更强大、更合理的异步方法,通过这篇文章,我想对目前已有JavaScript异步的处理方式做一个总结,同时试着去解释为什么会出现这些技术,让大家对JavaScript异步编程有一个更宏观的理解,让知识变得更体系化一些。 正文Step1 - 回调函数回调函数大家肯定都不陌生,从我们写一段最简单的定时器开始: setTimeout(function () { console.log('Time out');}, 1000);定时器里面的匿名函数就是一个回调函数,因为在JS中函数是一等公民,所以它可以像其他变量一样作为参数进行传递。这样看来,通过回调函数来处理异步挺好的,写着也顺手,为什么要用别的方法呢? 我们来看这样一个需求: 上面是微信小程序的登录时序图,我们的需求和它类似但又有些差别,想要获取一段业务数据,整个过程分为3步: 调用秘钥接口,获取key携带key调用登录接口,获取token和userId携带token和userId调用业务接口,获取数据可能上述步骤和实际业务中的有些出入,但是却可以用来说明问题,请大家谅解。 我们写一段代码来实现上述需求: let key, token, userId;$.ajax({ type: 'get', url: 'http://localhost:3000/apiKey', success: function (data) { key = data; $.ajax({ type: 'get', url: 'http://localhost:3000/getToken', data: { key: key }, success: function (data) { token = data.token; userId = data.userId; $.ajax({ type: 'get', url: 'http://localhost:3000/getData', data: { token: token, userId: userId }, success: function (data) { console.log('业务数据:', data); }, error: function (err) { console.log(err); } }); }, error: function (err) { console.log(err); } }); }, error: function (err) { console.log(err); }});可以看到,整段代码充满了回调嵌套,代码不仅在纵向扩展,横向也在扩展。我相信,对于任何人来说,调试起来都会很困难,我们不得不从一个函数跳到下一个,再跳到下一个,在整个代码中跳来跳去以查看流程,而最终的结果藏在整段代码的中间位置。真实的JavaScript程序代码可能要混乱的多,使得这种追踪难度会成倍增加。这就是我们常说的回调地狱(Callback Hell)。 ...

July 2, 2019 · 4 min · jiezi

fetch使用ajax替代方案

fetch简介Fetch 提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应。它还提供了一个全局 fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。 这种功能以前是使用 XMLHttpRequest实现的,Fetch提供了一个更好的替代方法 Fetch API 是基于 Promise 设计,使用了Promises 来处理结果/回调。旧浏览器不支持 Promise,需要使用 polyfill es6-promise 。 简单实现 fetch("http://192.168.43.169:8099/someInfo",{ method: 'post', }) .then(res=>{ console.log(response) // 包含响应结果的promise,只是一个 HTTP 响应,而不是真的JSON return response.json(); }) .then(res=>{ console.log(res) //js格式的json对象 })async await实现更方便 const fetchRequest = async () => { let response = await fetch("http://192.168.43.169:8099/teacher/resume", { method: 'post', }) let data = await response.json() console.log(data); //js格式的json对象 } fetchRequest()Response 对象属性: status:整数(默认值为200) 为response的状态码statusText: 字符串(默认值为"OK"),该值与HTTP状态码消息对应.ok:如上所示, 该属性是来检查response的状态是否在200-299(包括200,299)这个范围内.该属性返回一个Boolean值.方法:处理包含返回结果的promise对象的数据 例如 response.json()处理包含json结果的promise对象 ...

June 28, 2019 · 2 min · jiezi

对Promise中的resolverejectcatch的理解

对promise这个概念之前已经有了一些浅显的理解,相关文章->戳这里,最近又有了一些新的理解。 .then()的时候到底是在then什么…首先要理解…Promise是一个对象,有then()方法的对象then()的入参是一个函数,通常在promise链中,入参是 一个返回promise的函数 ,这句话好像有点拗口,就是说入参是一个函数,这个函数会return一个promise对象如何破坏promise链如果有这样一个promise链: p1().then(p2).then(p3) .then(function(data) { console.log('data: ' + data); }) .catch(function(error) { console.log('error: ' + error); }); function p1() { return new Promise(function(resolve, reject) { console.log('p1 resolved'); resolve(123); });} function p2() { return new Promise(function(resolve, reject) { console.log('p2 rejected'); reject(456); });} function p3() { return new Promise(function(resolve, reject) { console.log('p3 resolved'); resolve(789); });}上面这个例子,你看到的console.log会是这样: p1 resolvedp2 rejectederror: 456 并没有看到 p3 的log,而是看到了error message,也就是说: 在一个promise链中,只要任何一个promise被reject,promise链就被破坏了,reject之后的promise都不会再执行,而是直接调用.catch方法。这也是为什么在standard practice中,一定要在最后加上 .catch 的原因。通过 .catch 能够清楚的判断出promise链在哪个环节出了问题。 ...

June 19, 2019 · 1 min · jiezi

promise知识点

resolve 和 reject 函数被调用时,分别将promise的状态改为fulfilled(完成)或rejected(失败)

June 18, 2019 · 1 min · jiezi

关于promise的学习和拓展

Promise的学习和拓展以前开发的时候偶尔会在请求中,或者其他场景中用到promise,只知道它是什么(链式调用,用于请求的后返回值得操作之类的),大概怎么用,却没有深入了解。 起因:(在参考了廖雪峰的promise讲解后https://www.liaoxuefeng.com/w...) 在javascript中,所有的代码都是单线程进行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现(否者在加载完js文件后。其他操作也不会发生了): 参数:executor是带有 resolve 和 reject 两个参数的函数 。Promise构造函数执行时立即调用executor 函数, resolve 和 reject 两个函数作为参数传递给executor(executor 函数在Promise构造函数返回所建promise实例对象前被调用)。resolve 和 reject 函数被调用时,分别将promise的状态改为fulfilled(完成)或rejected(失败)。executor 内部通常会执行一些异步操作,一旦异步操作执行完毕(可能成功/失败),要么调用resolve函数来将promise状态改成fulfilled,要么调用reject 函数将promise的状态改为rejected。如果在executor函数中抛出一个错误,那么该promise 状态为rejected。 由此可以知道,一个promise包含3个状态:(注意,不包含resolve)pending: 初始状态,既不是成功,也不是失败状态。fulfilled: 意味着操作成功完成。rejected: 意味着操作失败。function callback() { console.log('Done');}console.log('before setTimeout()');setTimeout(callback, 0); // 1秒钟后调用callback函数console.log('after setTimeout()');观察上述代码执行,在Chrome的控制台输出可以看到: before setTimeout()after setTimeout()DoneAJAX就是典型的异步操作var ajax = ajaxGet('http://...');ajax.ifSuccess(success) .ifFail(fail);统一执行AJAX逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用success函数或fail函数。这个时候promise方法就应运而生了。一个简单的promise应用生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:function test(resolve, reject) { var timeOut = Math.random() * 2; log('set timeout to: ' + timeOut + ' seconds.'); setTimeout(function () { if (timeOut < 1) { log('call resolve()...'); resolve('200 OK'); } else { log('call reject()...'); reject('timeout in ' + timeOut + ' seconds.'); } }, timeOut * 1000);}有了执行函数,我们就可以用一个Promise对象来执行它,并在将来某个时刻获得成功或失败的结果: ...

May 31, 2019 · 1 min · jiezi

Promiseasyncawait深入了解

1、Promise 的含义Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。 Promise对象有以下两个特点。 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、resolved(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。 Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

May 30, 2019 · 1 min · jiezi

浅解Promise

对于异步的解决方案PromisePromise是ES6新增的内置对象,通常使用Promise构造函数创建,Promise实例,解决异步问题。 1.Promise构造函数必须接收一个函数作为参数,我将其称为executor函数,executor函数也可以接收两个参数,resolve和reject,它们是两个函数,由ECMAScript运行环境提供,无需自己部署。 //执行Promise函数,它返回一个promise实例对象let promise=new Promise ((resolve,reject)=>{ console.log(1);}); 当我们使用new创建一个构建一个Promise实例后,Promise实例内部的两个属性需要理解。 让我们输出上方的promise实例 [[PromiseStatus]]: "pending"[[PromiseValue]]: undefined[[PromiseStatus]]:保存当前promise实例的状态,可能值pending,resolved,rejected [[PromiseValue]]:在promise未完成状态时,其值为undefinde,当promise完成后,其值为异步数据,或者错误信息。当执行了resolve(),[[PromiseValue]]的值就是resolve()传入 的表达式的值,通常就是是我们需要的值。当我们使用了reject(),这个值为reject()的传入值,通常这个值为错误提示 2.对于resolve和reject,它们的作用是判断对象的状态。并且它们调用时可以接收一个参数。 let promise=new Promise ((resolve,reject)=>{ console.log(1); resolve('成功');});当调用了resolve或reject ,promise实例的[[PromiseStatus]]将发生改变。 现在,让我们更加详细的去了解Promisepromise状态一个Promise对象的当前状态必须为一下三种状态的一种:Pending(等待状态),Fulfilled(执行状态),Rejected(拒绝状态) Pending:处于Pending状态时,promise可以满足以下条件 可以由Pending转为Fulfilled或Rejected , Fulfilled:处于Fulfilled状态时,promise满足以下条件 1.不可再变为其它状态。 2.必须有一个不可被改变的值。 Rejected: 1.不可再变为其它状态。 2.必须有一个不可被改变的值。 如何理解Promise A + 规范中的这三个状态及其规则。Promise对象的两个内部属性可以很好帮我们去解释理解。 [[PromiseStatus]] ,[[PromiseValue]] 现在,进行Promise第一步,新建一个Promise对象,对象两个内部属性被初始化 [[PromiseStatus]] :Pending [[PromiseValue]]:undefined 此时是规范中的第一种状态,OK,根据规范的Pending状态的条件,我们可以去改变其状态。 executor函数在Promise对象创建后立即执行。 倘若我们在executor函数执行了resolve()函数,并且,将一个表达式(Expression)作为其参数传入。此时内部属性值的变化[[PromiseStatus]]:resolved [[PromiseValue]]:<表达式的值> 此时,Promise对象的状态有Pending变为Fulfilled.通俗点说,就是由等待变为成功。根据Promise A+ 的Fulfilled状态的规范条件,此时,无论接下来在遇到什么情况,都不会去改变它的状态!,并且Promise对象将会有一个值,Yes,这个值就是我们需要的值,准确的说,它就是那个传入的表达式的值,并且这个值通过resolve()函数存入了[[PromiseValue]]属性中。我们无法直接去使用它,而是通过then()去获取 让我们来看个例子 let p1 = new Promise((res,rej)=>{ res((() => { return '成功了'; })()) })console.log(p1); //输出[[PromiseStatus]]: "resolved"[[PromiseValue]]: "成功了"倘若我们在executor函数执行了reject()函数,并且,将一个表达式(Expression)作为其参数传入。此时内部属性值的变化let p2 = new Promise((res,rej)=>{ rej((() => { return '失败了'; })())})console.log(p2);// promise对象的两个属性[[PromiseStatus]]: "rejected"[[PromiseValue]]: "失败了"此时,Promise对象的状态有Pending变为Fulfilled,由等待变为失败,根据Promise A+ 的Fulfilled状态的规范条件,此时,无论接下来在遇到什么情况,都不会去改变它的状态!并且Promise对象将会有一个值,同样也是那个传入的表达式的值,不过我们将这个值称为拒因或失败原因。 ...

May 29, 2019 · 1 min · jiezi

今日头条网红题-????-async-functions-和-promises哪个更快

题目如下 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')而v8和node10产出的结果有所不同。 v8运行结果???? node10运行结果???? 先说下async/await原理???? async 声明的函数,其返回值必定是 promise 对象,如果没有显式返回 promise 对象,也会用 Promise.resolve() 对结果进行包装,保证返回值为 promise 类型await 会先执行其右侧表达逻辑(从右向左执行),并让出主线程,跳出 async 函数,而去继续执行 async 函数外的同步代码如果 await 右侧表达逻辑是个 promise,让出主线程,继续执行 async 函数外的同步代码,等待同步任务结束后,且该promise 被 resolve 时,继续执行 await 后面的逻辑如果 await 右侧表达逻辑不是 promise 类型,那么 async 函数之外的同步代码执行完毕之后,会回到 async函数内部,继续执行 await 之后的逻辑-- 摘自 LucasHC ...

May 29, 2019 · 1 min · jiezi

总结异步编程的六种方式

异步编程众所周知 JavaScript 是单线程工作,也就是只有一个脚本执行完成后才能执行下一个脚本,两个脚本不能同时执行,如果某个脚本耗时很长,后面的脚本都必须排队等着,会拖延整个程序的执行。那么如何让程序像人类一样可以多线程工作呢?以下为几种异步编程方式的总结,希望与君共勉。 回调函数事件监听发布订阅模式PromiseGenerator (ES6)async (ES7)异步编程传统的解决方案:回调函数和事件监听 初始示例:假设有两个函数, f1 和 f2,f1 是一个需要一定时间的函数。 function f1() { setTimeout(function(){ console.log('先执行 f1') },1000)}function f2() { console.log('再执行 f2')}回调函数因为 f1 是一个需要一定时间的函数,所以可以将 f2 写成 f1 的回调函数,将同步操作变成异步操作,f1 不会阻塞程序的运行,f2 也无需空空等待,例如 JQuery 的 ajax。 回调函数的demo: function f1(f2){ setTimeout(function(){ console.log('先执行 f1') },1000) f2()}function f2() { console.log('再执行 f2')}效果如下: 总结:回调函数易于实现、便于理解,但是多次回调会导致代码高度耦合 事件监听脚本的执行不取决代码的顺序,而取决于某一个事件是否发生。 事件监听的demo $(document).ready(function(){ console.log('DOM 已经 ready')});发布订阅模式发布/订阅模式是利用一个消息中心,发布者发布一个消息给消息中心,订阅者从消息中心订阅该消息,。类似于 vue 的父子组件之间的传值。 发布订阅模式的 demo //订阅done事件$('#app').on('done',function(data){ console.log(data)})//发布事件$('#app').trigger('done,'haha')PromisePromise 实际就是一个对象, 从它可以获得异步操作的消息,Promise 对象有三种状态,pending(进行中)、fulfilled(已成功)和rejected(已失败)。Promise 的状态一旦改变之后,就不会在发生任何变化,将回调函数变成了链式调用。 Promise 封装异步请求demo export default function getMethods (url){ return new Promise(function(resolve, reject){ axios.get(url).then(res => { resolve(res) }).catch(err =>{ reject(err) }) })}getMethods('/api/xxx').then(res => { console.log(res)}, err => { console.log(err)})GeneratorGenerator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,使用该对象的 next() 方法,可以遍历 Generator 函数内部的每一个状态,直到 return 语句。 ...

May 15, 2019 · 1 min · jiezi

再谈谈-Promise-setTimeout-rAF-rIC

欢迎关注我的公众号睿Talk,获取我最新的文章: 一、前言Promise, setTimeout, requestAnimationFrame, requestIdleCallback 这几个概念相信很多人都很熟悉了,最近在看 React Fiber 源码的时候又对它们有了更深一层的认识,在此分享一下。下文将用 rAF 代表 requestAnimationFrame, rIC 代表 requestIdleCallback。 二、事件循环与帧事件循环和上面 4 个名词的基本概念在此不再啰嗦了,我们着重看下它们之间的关系。浏览器是一个 UI 系统,所有的操作最终都会以页面的形式展现,而页面的基本单位是帧。一帧中可能包括的任务有下面几种类型。 events: 点击事件、键盘事件、滚动事件等macro: 宏任务,如 setTimeoutmicro: 微任务,如 PromiserAF: requestAnimationFrameLayout: CSS 计算,页面布局Paint: 页面绘制rIC: requestIdleCallback理想情况下,页面会以 60 帧每秒的帧率来运行,但实际上每秒绘制多少帧是由多个因素决定的,下面举一些例子: 一个加载完成的静态页面,当用户没有进行交互的情况下,页面不需要重绘,帧率为 0。快速滚动页面的时候,可视区域的内容不断发生变化,浏览器会尽可能快的重绘页面,理想帧率为 60。假设页面有一个注册了回调的按钮,回调执行需要 500 毫秒。当点击按钮后再快速滚动页面,头 500 毫秒页面是卡住动不了的,后 500 毫秒会尽可能快的重绘页面,这时候理想帧率为 30。当使用 rAF 制作动画的时候,浏览器会尽可能快的重绘页面,桌面浏览器可能是 60 帧,移动浏览器可能是 30 帧。从上面的例子可以看出,页面的帧率不是固定的,是会动态变化的。比如某一帧中的任务占据大量时间的情况下,会影响到下一帧的执行。那么谁来调节帧率呢?显然只能依靠浏览器自身。作为开发者的我们是无法准确知道回调什么时候执行的。比如: function animation() { console.log('time: ', +new Date()); setTimeout(animate, 1000 / 60);}animation();上面的函数是假定浏览器以帧率 60 运行的,当帧率达不到的时候,2 帧之间回调可能执行了多次,也可能一次都不执行,简称掉帧。 所以在制作动画的时候,我们不能预设浏览器的帧率,正确的做法是通过 rAF 注册回调, 由浏览器来控制动画调用时机: ...

May 12, 2019 · 1 min · jiezi

promise使用过程中的一点领悟

各位 JavaScript 程序员,是时候承认了,我们在使用 promise 的时候,会写出许多有问题的 promise 代码。 当然并不是 promise 本身的问题,A+ spec 规范定义的 promise 非常棒。 在过去的几年中,笔者看到了很多程序员在调用 PouchDB 或者其他 promise 化的 API 时遇到了很多困难。这让笔者认识到,在 JavaScript 程序员之中,只有少数人是真正理解了 promise 规范的。如果这个事实让你难以接受,那么思考一下我在 Twitter 上出的题: 问:下面四个使用 promise 的语句之间的不同点在哪儿? doSomething().then(function () { return doSomethingElse();}); doSomethin().then(functiuoin () { doSomethingElse();}); doSomething().then(doSomethingElse()); doSomething().then(doSomethingElse);如果你知道这个问题的答案,那么恭喜你,你已经是一个 promise 大师并且可以直接关闭这个网页了。 但是对于不能回答这个问题的程序员中 99.9% 的人,别担心,你们不是少数派。没有人能够在笔者的 tweet 上完全正确的回答这个问题,而且对于 #3 最终答案也令我感到震惊,即便我是出题人。 答案在本文的底部,但是首先,笔者必须先探究一下 promise 为何如此复杂,为什么不管是新手还是专家都有被 promise 折磨的经历。同时,笔者也会给出自认为能够快速、准确理解 promise 的方法。而且笔者确信读过这篇文章之后,理解 promise 不会那么难了。 在此之前,我们先了解一下有关 promise 的一些常识。 Promise 的起源如果你读过有关 promise 的文章,你会发现文章中一定会提到 Callback hell,不说别的,在视觉上,回调金字塔会让你的代码最终超过屏幕的宽度。 ...

May 10, 2019 · 6 min · jiezi

化解使用-Promise-时的竞态条件

网络时代,创建现代软件时其中一个很大的限制是所需要的数据往往在远程服务器上。应用程序在等待网络请求时简单地锁死是不现实(甚至不可能)的。相反,我们必须让应用程序在等待时保持响应。。 为此,我们需要写出并发的代码。当应用的某一部分正在等待网络请求的响应时,其他部分必须继续运行。 Promise 对于编写非阻塞型的代码是很不错的工具,而且你的浏览器就支持这个。 Promise 能让潜在可怕的异步代码变得非常友好。下面假设一个博客的文章视图这样从远程服务器加载一篇文章并显示它: // Called from `componentWillMount` and `componentWillReceiveProps`:ArticleView.prototype.updateArticle = function (props) { this.setState({ error: null, title: null, body: null }); ArticleStore.fetch(props.articleID).then(article => { this.setState({ title: article.title, body: article.body }); }).catch(err => { this.setState({ error: 'Oh Noes!' }); });};注意:这个例子使用了 React,但是这个概念适用于绝大多数前端视图系统。 这样的代码是很优雅的。许多复杂的异步调用消失了,取而代之的是直接明了的代码。然而,使用 promise 并不能保证代码是正确的。 注意到我例子中引入的不易察觉的竞态条件了吗? 提示:竞态条件出现的原因是无法保证异步操作的完成会按照他们开始时同样的顺序。 轮子掉了 为了阐明竞态条件,假设有这样一个左侧是文章列表,右侧是选中的文章内容的博客: App with Article 1 Selected 让我们从第一个选中的文章标题开始。然后,选中第二个文章标题。该应用发送一个请求去加载文章的内容(this.store.fetchArticle(2)),并且用户可以看见一个加载的指示器,就像这样: App with Article 2 Selected 因为网络原因,文章内容的加载需要一小会儿。数秒之后,用户觉得厌烦就(又)选择了第一篇文章。由于这篇文章已经加载过,它的内容几乎立即显示,应用仿佛回到最开始的状态。 App with Article 1 Reselected ...

May 9, 2019 · 1 min · jiezi

前端异步解决方案-4.2(generator+promise)

为什么要实现generator和promise的结合呢?大部分网上的文章都说是为了更简单的处理错误,不过这个我暂时还没有感悟,我反而觉得其实差不多;但是还是先学习一下用法吧;先从简单的用法讲起: //最简单的用法function request() { return new Promise(function (resolve, reject) { setTimeout(function () { let num = Math.random(); if (num > 0.9) { reject(num + "request err") } else { resolve(num * 100); } }, 100) })}let it = gen();let p = it.next().value;// 在yield处被返回的Promise 被赋给了 pp.then(res => it.next(res).value, err => it.throw(err));// 发生错误时,将错误抛入生成器(gen)中function* gen() { try { let response = yield request(); console.log(response.text); } catch (error) { console.log('Ooops, ', error.message); // 可以捕获Promise抛进来的错误!}} }}这里request的写法就是普通的Promise异步的写法,而gen中异步的写法就已经非常像同步了,唯一一个缺点就是中间这一段代码在我们多次异步的时候,我们需要不断的加长就像这样 ...

April 21, 2019 · 2 min · jiezi

「 JS 」快速上手异步方案

问题:解决异步回调的深层嵌套的问题.(回调地狱)1. Promisepromise对象用于表示一个异步操作的最终状态,promise在回调代码和将要执行这个任务的异步代码之间提供了一种可靠的中间机制来管理回调。//构造函数,回调函数是同步的回调new Promise(function(resolve,reject){ ….//异步操作})Promise的实例对象有三个状态 pending: 初始状态,既不是成功,也不是失败状态。fulfilled: 意味着操作成功完成。rejected: 意味着操作失败resolve和reject分别是两个函数,当在回调中调用时,会改变promise实例的状态,resolve改变状态为成功,reject为失败.thenPromise.prototype.then()当promise对象的状态发生改变时,绑定在其身上的then方法就会被调用。then方法包含两个参数:onfulfilled函数 和 onrejected函数,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争,then() 方法返回一个 Promise对象.返回值then方法返回一个新的Promise,而它的行为与then中的回调函数的返回值有关:如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。catchcatch() 方法返回一个Promise,并且处理拒绝的情况。Promise.prototype.catch()事实上,catch方法相当于then方法的第二个参数方法,触发拒绝状态.注意,如果调用 then的 Promise 的状态(fulfillment 或 rejection)发生改变,但是 then 中并没有关于这种状态的回调函数,那么 then 将创建一个没有经过回调函数处理的新 Promise 对象,这个新 Promise 只是简单地接受调用这个 then 的原 Promise 的终态作为它的终态。所以在链式上,最终会执行到catch上.//链式示例new Promise(function (resolve, reject) {setTimeout(function () {console.log(“1”);resolve();}, 1000);}).then(function () {return new Promise(function (resolve, reject) {setTimeout(function () {console.log(“2”);// resolve();reject();}, 1000);});}).then(function () {return new Promise(function (resolve, reject) {setTimeout(function () {console.log(“3”);resolve();}, 1000);});}).then(function () {return new Promise(function (resolve, reject) {setTimeout(function () {console.log(“4”);resolve();}, 1000);});}).catch(function(){console.log(“catch”);})2. genaratorsymbol新的一种基础数据类型symbol,表示独一无二的值.它通常作为对象的属性键,内置对象普遍存在该值.// 一般用法,它并不是构造器,不能通过new,会报错.let sym = Symbol();// 在对象中表现形式,要用[]包裹,不然会被认为是string.var obj = { [Symbol()]:“value”;}该属性是匿名,所以不可枚举,只能通过.getOwnPropertySymbols()返回的数组.// 想要获得两个相同的Symbol,得通过.for()Symbol(“asd”) === Symbol(“asd”) // falseSymbol.for(“asd”) === Symbol.for(“asd”) // trueIterator迭代器,存在于特定几种可枚举的数据类型中.// 一般用以下这种形式的键保存了迭代器函数.// arr[Symbol.iterator]aarrSymbol.iterator.next( ) //遍历下一个,返回value和done,value表示值,done表示是否可以继续遍历下一个.//for…of循环遍历就是基于此,必须该数据类型有迭代器.回到generator//表现形式function* test(){ yield 1; //任务1 yield 2; //任务2 yield 3; //任务3 yield 4 ; //任务4}// 调用该方法会返回一个含有迭代对象的对象.var obj = text();obj.next(); //调用该方法时,每次到一个yield处停止.3. async/await作用:简化promise的使用编码, 不通过then()/catch()来指定回调函数以同步编码方式实现异步流程async function test (){ // 等待状态改变,自动执行到下一个wait处 var flag = await new Promise((resolve,reject)=>{ setTimeout(function(){ // 状态改变 resolve(data); //这里面的值传递给flag },1000) }) //通过flag传递数据 flag = await new Promise((resolve,reject)=>{ setTimeout(function(flag){ // 状态改变 resolve(flag); },1000,flag) })}test().catch(function(err){ //处理异常}); ...

April 18, 2019 · 1 min · jiezi

前端异步解决方案-4.1(generator+promise)

文章还未写完,发布只是为了看排版和图片效果前言终于开始写generator了,离这个系列的终结又进了一步。其实generator我还处在会用但是不理解原理的状态,但是知识不总结,不记录的话容易忘记,所以我还是把现在的一点心得记录下来。等到以后有了更深的理解再回来补充。想要看更深度解析generator的朋友可以移步漫话JavaScript与异步·第三话——Generator:化异步为同步这里面谈及了generator的底层实现及generator的用法。是我看过的文章中自认为解释的最好的一篇,而且篇幅也不长,建议大家去看一看。实现根据一贯的作风,我们先尝试自己实现generator尝试ing…………好了尝试完了,实现不了,老老实实的学习generator的用法吧。用法在我的理解中,generator最大的特点就是可以让函数在特定的地方停下,等待被唤醒后在函数内部环境中继续执行。我们结合代码来看一看:注释:【1】Iterator Object对象:参考 Iterator 文章比较长,但是如果只是想要了解什么是Iterator Object的话看完第一小节就足够了//输出分割线的函数,感兴趣的可以自行百度如何设置console.log的样式function cut_off(color) { console.log("%c——————————————",“color:"+color+";font-size:20px”);}//* 为generator函数的标识,如果我们想要创建一个generator函数就必须在function后面加上function generator() { let num1, num2; num1 = 123; console.log(“num1”, num1, “num2”, num2); //yield就是该函数内部暂停的地方,暂停的同时会把yield后面的值返回出去 yield num1; num2 = 456; console.log(“num1”, num1, “num2”, num2); yield num2; console.log(“num1”, num1, “num2”, num2); return “end”}console.log(“generator defined”);//函数返回一个Iterator Object对象;// 但是与普通函数不同的是,这个时候函数并不执行函数内部的代码let g = generator();console.log(“g defined”);cut_off(“red”);console.log(“g.next() run 1”);//开始执行函数内部的代码,并且遇在到yield的时候返回 yield后面的值console.log(g.next());cut_off(“red”);console.log(“g.next() run 2”);//从上次执行完的地方执行,并且遇在到yield的时候返回 yield后面的值console.log(g.next());cut_off(“red”);console.log(“g.next() run 3”);//从上次执行完的地方执行,这次是最后一次有值的返回,done的状态会变为trueconsole.log(g.next());cut_off(“red”);console.log(“g.next() run 4”);//已经执行完成之后再次被调用,永远返回{value:undefined, done: true}console.log(g.next());cut_off(“red”);console.log(“g.next() run 5”);//已经执行完成之后再次被调用,永远返回{value:undefined, done: true}console.log(g.next());贴上一张代码和运行结果的对比图

April 18, 2019 · 1 min · jiezi

前端异步解决方案-3(Promise)

又有好些天没有动笔了,这几天一直在断断续续的学习Promise和generator,今天终于能够把着两个玩意结合起来了解决异步问题了。今天我先把promise相关的用法和对异步的处理分享给大家。老样子,还是先模拟一个Promise。//咳咳这样就实现了嘛let MyPromise = Promise;开个玩笑,其实这两天我也一直在看Promise的实现,但是还是没有怎么理解。所以Promise的代码实现我暂时先放一放,等我完全理解再来新开一篇分享。这里先给大家推荐几篇我觉得比较好的Promise实现的博客,想要学习的小伙伴可以先到那边一睹为快,当然等我更新了之后大家还是要来给我文章点赞的哈。彻底理解Promise对象——用es5语法实现一个自己的Promise(上篇)这一篇文章用的es5的语法,对es6不是很熟悉的同学可以用这篇文章顶一下Promise实现原理(附源码)这一篇呢是用到了class的,所以需要大家对es6有所了解,也是一篇好文章接下来我介绍一下Promise最常用的几个部分:首先先介绍最简单的只有一个Promise对象的用法//Promise()接收一个函数,并且在创建这个Promise对象的同时,接收的这个函数就会被立刻执行var promise = new Promise(function (resolve, reject) { //resolve用来接收成功的返回,reject用来接收失败的返回 setTimeout(function () { //这里我们生成一个随机数,并在接下来根据这个随机数的大小来判断这个异步是否成功 let num = Math.random(); if (num > 0.8) { //接收失败的原因 console.log(“reject”) reject(num + “大于0.8,这次异步操作失败了”) } else { //接收成功的数据 console.log(“resolve”) resolve(num + “小于0.8,这次异步操作成功了”) } }, 100)});console.log(“一个Promise对象生成了”);//Promise对象的.then()方法接收两个回调,第一个为成功回调,第二个为失败回调//这两个回调会在上面的resolve或者reject函数生效后被调用promise.then(function (data) { //这个函数会在上面的resolve函数被调用后生效,这里的data就是上面resolve()中接收的值 console.log(data)}, function (err) { ///这个函数会在上面的reject函数被调用后生效,这里的err就是上面reject()中接收的值 console.error(err)});console.log(“promise的.then方法被调用了”)大家可以按F12唤起控制台,然后把这段代码运行几次。看看会有什么结果; 多运行几次以后,大家应该可以看到这两类结果: 其中的undefind是console.log(“promise的.then方法被调用了”)这行代码的返回,大家可以不用关注。在这里可以看到不论是成功的结果还是失败的结果都是在定时器执行后再打印出来的。这种写法可以帮助我们实现简单的异步编程。接下介绍多个Promise对象同时使用的用法, 先介绍最常见的.then()链式调用的方法 //用来快速生成一个Promise对象,接收一个日志列表,不论成功还是失败都会往日志列表中添加一条日志 function promise(log) { return new Promise(function (resolve, reject) { setTimeout(function () { log = log || []; //和上次的例子一样,利用随机数来随机失败和成功 let num = Math.random(); if (num > 0.5) { log.push(num + “大于0.5,这次异步操作失败了”); reject(log) } else { log.push(num + “小于0.5,这次异步操作成功了”); resolve(log) } }, 100) }) }.then()中返回了Promise对象的情况var promise1 = promise();//promise1.then()方法会返回一个Promise对象//如果我们在.then()生效的那个 !!!回调方法!!! 中有返回一个Promise对象的话,该对象会被 !!!.then()方法!!! 返回//先看返回了Promise对象的方式promise1.then(function (data) { console.log(data); return promise(data)}, function (err) { console.error(err); return promise(err)}).then(function (data) { console.log(data)}, function (err) { console.error(err)});这段代码运行后一共会有四种结果: 两次都成功 两次都失败 第一次失败,第二次成功 第一次成功,第二次失败通过这种方法我们可以用比较清晰的方式来书写我们的异步代码。特别是多个异步操作嵌套的时候,可以链式调用.then()来实现,这样的代码看起来逻辑更清晰; 刚刚看完了返回了Promise对象的场景,再来看看没有返回Promise的场景//如果我们没有返回Promise对象,.then()就会将我们返回的东西包装成一个Promise对象(没有返回就相当于返回了undefined)//可以等同于我们写了 return new Promise((resolve,reject)=>{resolve(/原先的返回值/)})promise1.then(function (data) { console.log(data); return data;}, function (err) { console.error(err); return err;}).then(function (data) { console.log(data)}, function (err) { //这里是永远不会被触发的,原因是上一个.then() 返回的是new Promise((resolve,reject)=>{resolve(/原先的返回值/)}) //返回的Promise对象的reject方法永远都不会被触发,所以这个里也就永远都不会触发了 console.error(err)});讲解都写在注释里面了,接下里我就贴运行图吧,这段代码会运行出以下两类结果:需要所有的请求都返回后才可以执行某个动作//改造promise,让其可以接收一个定时器等待时间参数function promise(log, time) { return new Promise(function (resolve, reject) { setTimeout(function () { log = log || []; //和上次的例子一样,利用随机数来随机失败和成功 let num = Math.random(); if (num > 0.5) { log.push(“等待时长” + time + “,” + num + “大于0.5,这次异步操作失败了”); console.error(log); reject(log) } else { log.push(“等待时长” + time + “,” + num + “小于0.5,这次异步操作成功了”); console.log(log); resolve(log) } }, time) })}//Promise.all()可以接收一个Promise对象的数组,返回一个Promise对象//该Promise对象会在数组中所有Promise成功返回后执行成功回调,在任意一个Promise失败后立刻执行失败回调var promise1 = promise(null, 10), promise2 = promise(null, 100), promise3 = Promise.all([promise1, promise2]);promise3.then((data) => { //这里的data为promise1,和promise2的返回值的数组 console.log(“promise3”, data)}, (err, err2) => { //报错信息 console.error(“promise3”, err)});这段代码一共有四种可能的结果 如果两次都成功的话 如果两次都成的话,promise3会执行成功回调,并且回调中的data就是promise1和promise2返回值的数组(数组顺序和.all()中的顺序一致)任意一个promise失败的话,promise3会立刻执行失败回调,并且回调中的err就是失败的那个promise在reject中返回的值文章写到这里,我认为Promise常用的一些用法都已经讲完了,更详细的Promise的教程请参考 MDN中对promise的讲解 ...

April 16, 2019 · 2 min · jiezi

JavaScript ES6相关的一些知识(/let、const/箭头函数/Promise/generate)

ES6是个啥ECMAScript是国际通过的标准化脚本语言JavaScript由ES,BOM,DOM组成ES是JavaScript的语言规范,同时JavaScript是ES的实现和扩展6就是JavaScript语言的下一代标准关于ES6的一些知识1.let、constES5中的作用域有:函数作用域,全局作用域ES6新增了块级作用域。由{}包括(if和for语句也属于块级作用域){ var a = 1; let b = 2; }console.log(a)//1console.log(b)//undefinedlet、const、var的区别var:可以跨块级作用域访问,不能跨函数作用域访问let:只能在块级作用域访问,不能跨函数使用const:定义常量,必须初始化且不能修改,只能在块级作用域内使用关于变量提升:var不论声明在何处都会莫默认提升到函数/全局最顶部,但是let和const不会进行变量提升2.arrow function 箭头函数箭头函数相当于匿名函数,简化了函数的定义定义就用=> 一个箭头箭头函数有两种格式:第一种:只包含一个表达式x=>x++相当于function(x)}{ return x++;}第二种:包含多条语句://包含判断等x=>{ if(x>0){ return x++; } else{ x–; }}//多个参数(x,y,z….)=>x+y+z+…+//无参数()=>1//返回对象x=>({obj:x})//注意符号,避免和函数体的{}冲突使用箭头函数时,函数体内的this对象,就是定义时所在的对象3.Promise定义:Promise对象用于异步操作,它表示一个尚未完成且预计在未来完成的异步操作关于同步&异步JavaScript是基于事件驱动的单线程运行机制(why: 浏览器中至少有三个线程:js引擎线程,gui渲染线程,浏览器事件触发线程js操作dom,浏览器事件触发都会影响gui渲染效果,因此他们之间存在互斥的关系,使用多线程会带来非常复杂的同步问题(a线程在某个DOM节点添加内容。b线程删除了该节点)同步:即单线程模式,所有的任务都在主线程上执行,形成一个执行栈*,如函数调用后需要等待函数执行结束后才能进行下一个任务,如果某个任务执行时间过长(如死循环),容易造成线程阻塞,影响下面任务的正常进行异步:可以一起执行多个任务,函数调用后不会立刻就返回执行结果,异步任务会在当前脚本所有的同步任务执行结束后再执行。异步任务不进入主线程,而是进入任务队列,在某个任务可以执行时,等待主线程读取任务队列,随后该任务将进入主线程执行异步任务的实现,最经典的就是setTimeout()/setInterval()他们的内部运行机制完全一样,前者指定的代码只执行一次,后者为反复执行setTimeout(function(){ console.log(“taskA,yibu”);},0)console.log(“taskB,tongbu”);//taskB,tongbu//taskA,yibu即使延时事件为0,但由于它属于异步任务,仍需要等待同步任务执行结束后再执行综合看,整体的执行顺序为:先执行执行栈中的内容,执行完毕后,读取任务队列,寻找对应的异步任务,结束等待状态,进入执行栈执行,不断循环(Event Loop)只要主线程空了,就会去读取任务队列关于回调函数,callback()回调函数即是会被主线程挂起的代码异步任务必须指定回调函数,当主线程开始读取任务队列,执行异步任务的时候,执行的就是对应的回调函数言归正传,继续理解Promisepromise的三种状态:pending:初始状态fulfilled:操作成功rejected:操作失败Promise可以由1->2/1->3一旦状态变化,便会一直保持这个状态,不再改变。当状态改变Promise.then绑定的函数就会被调用构建Promisevar promise = new Promise(function(resolve,reject){ if(/操作成功/) resolve(data); else reject(error);});异步操作成功调用resolve,将结果作为参数传递出去异步操作失败调用reject,将报出的错误作为参数传递出去Promise构建完成后,使用then方法指定resolve状态和reject状态的回调函数promise.then(成功回调函数,失败的回调函数(非必要))//这两个函数都接受promise传出的值作为参数promise.then(function(data){do xxxx for success},function(error){do xxxx for failure});Promise新建后就执行,then方法指定的回调函数会在当前脚本的所有同步任务执行结束后再执行例子:var promise = new Promise(function(resolve, reject) { console.log(‘before resolved’); resolve(); console.log(‘after resolved’);});promise.then(function() { console.log(‘resolved’);});console.log(‘outer’);//before resolved//after resolved//outer//resolvedPromise的优势在于,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。能够简化层层回调的写法。Promise的精髓在于,用维护状态、传递状态的方式使得回调函数能够及时调用,比传递callback要简单、灵活Promise的其他方法.catch()用于指定发生错误时的回调函数,等同于reject部分和reject的区别:promise.then(onFulfilled,onRejected)在onFulfilled发生异常,在onRejected中捕获不到promise.then(onFulfilled).catch(onRejected)能够捕获异常。也可以用then替换,只是写法不同。本质上没有区别.all()用于将多个Promise实例包装成一个新的Promise实例var p = Promise.all([p1, p2, p3]);p1p2p3都需为promise实例当p1p2p3都为fulfilled时,p才会变为fulfilled只要有一个变为rejected,p就会变成rejected.race()用于将多个Promise实例包装成一个新的Promise实例与all()的区别类似于 AND 和 ORp1p2p3有一个状态发生改变,p的状态就发生改变,并返回第一个改变状态的promsie返回值,传递给p.resolve()看作new Promise()的快捷方式实例:Promise.resolve(‘Success’);/等同于/new Promise(function (resolve) { resolve(‘Success’);});让对象立刻进入resolved状态4.generate可以将generate理解为一个能够多次返回的“函数”function* foo(x){ yield x++; yield x+2; yield x+3; return xx;}由function定义,并且yield也可以返回数据调用generate的方法:不断的使用next()var f = foo(0);console.log(f.next());// 0 falseconsole.log(f.next());// 1 falseconsole.log(f.next());// 3 falseconsole.log(f.next());// 4 true使用for of结构for (var x of foo(0)) { console.log(x); // 依次输出0 1 3 (没有输出4原因不详)}每执行一次后就暂停,返回的值就是yield的返回值,每次返回一个值,直到done为true,这个generate对象已经全部执行完毕generate更像一个能够记住执行状态的函数实际上generate不算是一个函数,它的返回值不是变量也不是函数,而是一个可迭代的对象该对象类似一个元素被定义好的数组,保存的是一种规则而不元素本身,不能够随机访问,遍历也只能够遍历一次,因为规则只保存了上次的状态参考文档1:讲解JavaScript的线程运作参考文档2:讲解Promise参考文档3:关于generate ...

April 16, 2019 · 1 min · jiezi

实现Promise的first等各种变体

原文地址: https://www.xiabingbao.com/po…本篇文章主要是想通过ES6中Promise提供的几个方法,来实现诸如first、last、none、any等各种变体方法!在标准的ES6规范中,提供了Promise.all和Promise.race两种,我们首先来了解下这两个方法是干嘛的,方便我们后面工作的展开。Promise.all中所有的Promise实例都处于完成状态,该方法才进入完成状态,否则任意一个被拒绝,则该方法进入拒绝状态,并舍弃其他所有完成的结果,拒绝原因是第一个被拒绝的实例的原因。Promise.race中任意的一个Promise实例变成完成状态或者拒绝状态,则race结束,race的结果即为第一个变成最终状态的结果!更详细的可以参考下阮一峰的文章Promise对象之Promise.all。1. 准备工作在开始编写各种变体方法之前,这里我们首先定义几个一会儿要使用的几个Promise实例:/** * 创建一个Promise对象的实例 * @param name {string} 该实例的名称 * @param flag {boolean} 返回的结果状态:完成还是拒绝 * @param diff {number} 延迟的时间 */var createPromiseCase = ( name, flag, diff ) => { return new Promise( ( resolve, reject ) => { setTimeout( () => { flag ? resolve( name ) : reject( new Error( ’testPromise is error, name: ’ + name ) ); }, diff ); } );};var p1_suc_100 = createPromiseCase( ‘p1-suc-100’, true, 100 );var p2_suc_500 = createPromiseCase( ‘p2-suc-500’, true, 500 );var p3_suc_300 = createPromiseCase( ‘p3-suc-300’, true, 300 );var p4_fail_400 = createPromiseCase( ‘p4-fail-400’, false, 400 );var p5_fail_200 = createPromiseCase( ‘p5-fail-200’, false, 200 );2. 各种变体方法2.1 Promise.first场景:一个页面当前正处于loading状态,同时请求了多个接口,无论哪个接口正确返回结果,则loading效果取消!或者其他的要获取获取第一个完成状态的值。这里就要用到了Promise.first了,只要任意一个Promise实例变成完成状态,则Promise.first变成完成状态。其实这里并不适合Promise.race方法,因为第一个变成拒绝状态的实例也会激活Promise.race,if ( !Promise.first ) { // get first resolve result Promise.first = promiseList => { return new Promise( ( resolve, reject ) => { var num = 0; var len = promiseList.length; promiseList.forEach( pms => { Promise.resolve( pms ).then( resolve ).catch( () => { num++; if ( num === len ) { reject( ‘all promises not resolve’ ); } } ); } ); } ); };}调用方式:Promise.first([p4_fail_400, p2_suc_500, p3_suc_300]) .then(res => console.log(res)) // p3-suc-300 .catch(e => console.error(e))可以看到每次获取的p3_suc_300的值,因为p4是失败的状态,p2的完成状态没有p3快,因此这里获取到了p3的结果。2.2 Promise.last与Promise.first对应的则是Promise.last,获取最后变成完成状态的值。这里与Promise.first不同的是,只有最后一个Promise都变成最终态(完成或拒绝),才能知道哪个是最后一个完成的,这里我采用了计数的方式,then和catch只能二选一,等计数器达到list.length时,执行外部的resolve。if ( !Promise.last ) { // get last resolve result Promise.last = promiseList => { return new Promise( (resolve, reject) => { let num = 0; let len = promiseList.length; let lastResolvedResult; const fn = () => { if (++num===len) { lastResolvedResult ? resolve(lastResolvedResult) : reject(‘all promises rejected’); } } promiseList.forEach( pms => { Promise.resolve( pms ) .then(res => { lastResolvedResult = res; fn() }) .catch(fn); } ) } ) }}调用方式:Promise.last([p1_suc_100, p2_suc_500, p5_fail_200, p3_suc_300, p4_fail_400]) .then(res => console.log(res)) // p2-suc-500 .catch(e => console.error(e))p2需要500ms才能完成,是最晚完成的。2.3 Promise.nonePromise.none与Promise.all正好相反,所有的promise都被拒绝了,则Promise.none变成完成状态。该方法可以用Promise.first来切换,当执行Promise.first的catch时,则执行Promise.none中的resolve。不过这里我们使用Promise.all来实现。if ( !Promise.none ) { // if all the promises rejected, then succes Promise.none = promiseList => { return Promise.all( promiseList.map( pms => { return new Promise( ( resolve, reject ) => { // 将pms的resolve和reject反过来 return Promise.resolve( pms ).then( reject, resolve ); } ) } ) ) }}调用方式:Promise.none([p5_fail_200, p4_fail_400]) .then(res => console.log(res)) .catch(e => console.error(e))// then的输出结果:// [// Error: testPromise is error, name: p5-fail-200, // Error: testPromise is error, name: p4-fail-400// ]两个promise都失败后,则Promise.none进入完成状态。2.4 Promise.anyPromise.any表示只获取所有的promise中进入完成状态的结果,被拒绝的则忽略掉。if ( !Promise.any ) { // get only resolve the results Promise.any = promiseList => { let result = []; return Promise.all( promiseList.map( pms => { return Promise.resolve( pms ) .then( res => result.push( res ) ) .catch( e => { } ); } ) ).then( ( res ) => { return new Promise( ( resolve, reject ) => { result.length ? resolve( result ) : reject(); } ) } ) }}调用方式:Promise.any([p1_suc_100, p2_suc_500, p5_fail_200, p3_suc_300, p4_fail_400]) .then(res => console.log(res)) // [“p1-suc-100”, “p3-suc-300”, “p2-suc-500”] .catch(e => console.error(e))2.5 Promise.every最后一个的实现比较简单,所有的promise都进入完成状态,则返回true,否则返回false。if (!Promise.every) { // get the boolean if all promises resolved Promise.every = promiseList => { return Promise.all(promiseList) .then(() => Promise.resolve(true)) .catch(() => Promise.resolve(false)); }}调用方式:Promise.every([p1_suc_100, p2_suc_500, p3_suc_300]) .then(result => console.log(‘Promise.every’, result)); // Promise.every truePromise.every([p1_suc_100, p4_fail_400]) .then(result => console.log(‘Promise.every’, result)); // Promise.every false3. 总结Promise还有各种方面的应用,很多类库也都实现了类似的方法,这里也仅仅是鄙人拙见,稍微实现了Promise的变体方法,加深下对Promise的理解。原文地址: 蚊子的博客 ...

April 10, 2019 · 3 min · jiezi

JavaScript异步流程控制

JavaScript特性JavaScript属于单线程语言,即在同一时间,只能执行一个任务。在执行任务时,所有任务需要排队,前一个任务结束,才会执行后一个任务。当我们向后台发送一个请求时,主线程读取 “向后台发送请求” 这个事件并执行之后,到获取后台返回的数据这一过程会有段时间间隔,这时CPU处于空闲阶段,直到获取数据后再继续执行后面的任务,这就降低了用户体验度,使得页面加载变慢。于是,所有任务可以分成两种:同步任务和异步任务。同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务:不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制,这个过程会不断重复。“任务队列"是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"执行栈"了。主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。JavaScript异步实现的5种方式1. callback(回调函数)回调函数,也被称为高阶函数,是一个被作为参数传递给另一个函数并在该函数中被调用的函数。看一个在JQuery中简单普遍的例子:// 注意: click方法是一个函数而不是变量$("#button”).click(function() { alert(“Button Clicked”);}); 可以看到,上述例子将一个函数作为参数传递给了click方法,click方法会调用该函数,这是JavaScript中回调函数的典型用法,它在jQuery中广泛被使用。它不会立即执行,因为我们没有在后面加( ),而是在点击事件发生时才会执行。比如,我们要下载一个gif,但是不希望在下载的时候阻断其他程序,可以实现如下:downloadPhoto(‘http://coolcats.com/cat.gif', handlePhoto)function handlePhoto (error, photo) { if (error) { console.error(‘Download error!’, error); } else { console.log(‘Download finished’, photo); }}console.log(‘Download started’)首先声明handlePhoto函数,然后调用downloadPhoto函数并传递handlePhoto作为其回调函数,最后打印出“Download started”。请注意,handlePhoto尚未被调用,它只是被创建并作为回调传入downloadPhoto。但直到downloadPhoto完成其任务后才能运行,这可能需要很长时间,具体取决于Internet连接的速度,所以运行代码后,会先打印出Download started。这个例子是为了说明两个重要的概念:handlePhoto回调只是稍后存储一些事情的一种方式;事情发生的顺序不是从顶部到底部读取,而是基于事情完成时跳转;1. callback hell(回调地狱)var fs = require(‘fs’);/** * 如果三个异步api操作的话 无法保证他们的执行顺序 * 我们在每个操作后用回调函数就可以保证执行顺序 */ fs.readFile(’./data1.json’, ‘utf8’, function(err, data){ if (err) { throw err; } else { console.log(data); fs.readFile(’./data2.json’, ‘utf8’, function(err, data){ if (err) { throw err; } else { console.log(data) fs.readFile(’./data3.json’, ‘utf8’, function(err, data){ if (err) { throw err; } else { console.log(data); } }) } }) }})有没有看到这些以"})“结尾的金字塔结构?由于回调函数是异步的,在上面的代码中每一层的回调函数都需要依赖上一层的回调执行完,所以形成了层层嵌套的关系最终形成类似上面的回调地狱。2. 代码层面解决回调地狱1. 保持代码简短var form = document.querySelector(‘form’)form.onsubmit = function formSubmit (submitEvent) { var name = document.querySelector(‘input’).value request({ uri: “http://example.com/upload", body: name, method: “POST” }, function postResponse (err, response, body) { var statusMessage = document.querySelector(’.status’) if (err) return statusMessage.value = err statusMessage.value = body })}可以看到,上面的代码给两个函数加了描述性功能名称,使代码更容易阅读,当发生异常时,你将获得引用实际函数名称而不是“匿名”的堆栈跟踪。现在我们可以将这些功能移到我们程序的顶层:document.querySelector(‘form’).onsubmit = formSubmit;function formSubmit (submitEvent) { var name = document.querySelector(‘input’).value; request({ uri: “http://example.com/upload", body: name, method: “POST” }, postResponse);} function postResponse (err, response, body) { var statusMessage = document.querySelector(’.status’); if (err) return statusMessage.value = err; statusMessage.value = body;}重新整改代码结构之后,可以清晰的看到这段函数的功能。2. 模块化从上面取出样板代码,并将其分成几个文件,将其转换为模块。这是一个名为formuploader.js的新文件,它包含了之前的两个函数:module.exports.submit = formSubmit;function formSubmit (submitEvent) { var name = document.querySelector(‘input’).value; request({ uri: “http://example.com/upload", body: name, method: “POST” }, postResponse)}function postResponse (err, response, body) { var statusMessage = document.querySelector(’.status’); if (err) return statusMessage.value = err; statusMessage.value = body;}把它们exports后,在应用程序中引入并使用,这就使得代码更加简洁易懂了:var formUploader = require(‘formuploader’);document.querySelector(‘form’).onsubmit = formUploader.submit;3. error first处理每一处错误,并且回调的第一个参数始终保留用于错误:var fs = require(‘fs’) fs.readFile(’/Does/not/exist’, handleFile); function handleFile (error, file) { if (error) return console.error(‘Uhoh, there was an error’, error); // otherwise, continue on and use file in your code; }有第一个参数是错误是一个简单的惯例,鼓励你记住处理你的错误。如果它是第二个参数,会更容易忽略错误。除了上述代码层面的解决方法,还可以使用以下更高级的方法,也是另外4种实现异步的方法。但是请记住,回调是JavaScript的基本组成部分(因为它们只是函数),在学习更先进的语言特性之前学习如何读写它们,因为它们都依赖于对回调。2. 发布订阅模式订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。比如有个界面是实时显示天气,它就订阅天气事件(注册到调度中心,包括处理程序),当天气变化时(定时获取数据),就作为发布者发布天气信息到调度中心,调度中心就调度订阅者的天气处理程序。简单来说,发布订阅模式,有一个事件池,用来给你订阅(注册)事件,当你订阅的事件发生时就会通知你,然后你就可以去处理此事件。使用发布订阅模式,来修改Ajax:xhr.onreadystatechange = function () {//监听事件 if (this.readyState === 4) { if (this.status === 200) { switch (dataType) { case ‘json’: { Event.emit(‘data ‘+method,JSON.parse(this.responseText)); //触发事件 break; } case ’text’: { Event.emit(‘data ‘+method,this.responseText); break; } case ‘xml’: { Event.emit(‘data ‘+method,this.responseXML); break; } default: { break; } } } }}3. PromiseES6将Promise写进了语言标准,统一了用法,原生提供了Promise对象。Promise,简单说就是一个容器,里面保存着一个异步操作的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise有3种状态:pending(进行中)、fulfilled(成功)、rejected(失败)。Promise很重要的两个特点:状态不受外界影响;只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。一旦状态改变,就不会再变,任何时候都可以得到这个结果;Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为resolved(已定型)。1. 基本用法const p = new Promise((resolve,reject) => { // resolve在异步操作成功时调用 resolve(‘success’); // reject在异步操作失败时调用 reject(’error’);});p.then(result => { console.log(result);});p.catch(result => { console.log(result);})ES6规定,Promise对象是一个构造函数,用来生成Promise实例。new一个Promise实例时,这个对象的起始状态就是Pending状态,再根据resolve或reject返回Fulfilled状态 / Rejected状态。2. Promise.prototype.then( )前面可以看到,Promise实例具有then方法,所以then方法是定义在原型对象Promise.prototype上的,它的作用是为Promise实例添加状态改变时的回调函数。then方法返回的是一个新的Promise实例,因此then可以采用链式写法:getJSON("/posts.json”).then(function(json) { return json.post;}).then(function(post) { // …});3. Promise.prototype.catch( )Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。getJSON(’/posts.json’).then(function(posts) { // …}).catch(function(error) { // 处理 getJSON 和 前一个回调函数运行时发生的错误 console.log(‘发生错误!’, error);});4. Promise.all( )Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。const p = Promise.all([p1, p2, p3]);上面代码中,p的状态由p1、p2、p3决定,分成两种情况:只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。5. Promise.race( )Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。不同的是,race()接受的对象中,哪个对象返回快就返回哪个对象,如果指定时间内没有获得结果,就将Promise的状态变为reject。const p = Promise.race([ fetch(’/resource-that-may-take-a-while’), new Promise(function (resolve, reject) { setTimeout(() => reject(new Error(‘request timeout’)), 5000) })]);p.then(console.log).catch(console.error);上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。6. Promise.resolve( )Promise.resolve(‘foo’)// 等价于new Promise(resolve => resolve(‘foo’))7. Promise.reject( )const p = Promise.reject(‘出错了’);// 等同于const p = new Promise((resolve, reject) => reject(‘出错了’))p.then(null, function (s) { console.log(s)});// 出错了下面是一个用Promise对象实现的Ajax操作的例子:const getJSON = function(url) { const promise = new Promise(function(resolve, reject){ const handler = function() { if (this.readyState !== 4) { return; } if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; const client = new XMLHttpRequest(); client.open(“GET”, url); client.onreadystatechange = handler; client.responseType = “json”; client.setRequestHeader(“Accept”, “application/json”); client.send(); }); return promise;};getJSON("/posts.json”).then(function(json) { console.log(‘Contents: ’ + json);}, function(error) { console.error(‘出错了’, error);});8. callbackify & promisifyNode 8提供了两个工具函数util.promisify、util.callbackify用于在回调函数和Promise之间做方便的切换,我们也可以用JavaScript代码来实现一下。1. promisify:把callback转化为promisefunction promisify(fn_callback) { //接收一个有回调函数的函数,回调函数一般在最后一个参数 if(typeof fn_callback !== ‘function’) throw new Error(‘The argument must be of type Function.’); //返回一个函数 return function (…args) { //返回Promise对象 return new Promise((resolve, reject) => { try { if(args.length > fn_callback.length) reject(new Error(‘arguments too much.’)); fn_callback.call(this,…args,function (…args) { //nodejs的回调,第一个参数为err, Error对象 args[0] && args[0] instanceof Error && reject(args[0]); //除去undefined,null参数 args = args.filter(v => v !== undefined && v !== null); resolve(args); }.bind(this)); //保证this还是原来的this } catch (e) { reject(e) } }) }}2. callbackify:promise转换为callbackfunction callbackify(fn_promise) { if(typeof fn_promise !== ‘function’) throw new Error(‘The argument must be of type Function.’); return function (…args) { //返回一个函数 最后一个参数是回调 let callback = args.pop(); if(typeof callback !== ‘function’) throw new Error(‘The last argument must be of type Function.’); if(fn_promise() instanceof Promise){ fn_promise(args).then(data => { //回调执行 callback(null,data) }).catch(err => { //回调执行 callback(err,null) }) }else{ throw new Error(‘function must be return a Promise object’); } }}个人而言,最好直接把代码改成promise形式的,而不是对已有的callback加上这个中间层,因为其实改动的成本差不多。但总有各种各样的情况,比如,你的回调函数已经有很多地方使用了,牵一发而动全身,这时这个中间层还是比较有用的。4. generator(生成器)函数Generator函数是ES6提供的一种异步编程解决方案,通过yield标识位和next()方法调用,实现函数的分段执行。1. next( )方法先从下面的例子看一下Generator函数是怎么定义和运行的。function gen() { yield “hello”; yield “generator”; return;}gen(); // 没有输出结果var g = gen();console.log(g.next()); // { value: ‘hello’, done: false }console.log(g.next()); // { value: ‘generator’, done: false }console.log(g.next()); // { value: ‘undefined’, done: true }从上面可以看到,Generator函数定义时要带,在直接执行gen()时,没有像普通的函数一样,输出结果,而是通过调用next()方法得到了结果。这个例子中我们引入了yield关键字,分析下这个执行过程:创建了g对象,指向gen的句柄第一次调用next(),执行到yield hello,暂缓执行,并返回了hello第二次调用next(),继续上一次的执行,执行到yield generator,暂缓执行,并返回了generator第三次调用next(),直接执行return,并返回done:true,表明结束。经过上面的分析,yield实际就是暂缓执行的标示,每执行一次next(),相当于指针移动到下一个yield位置。next()方法返回的结果是个对象,对象里面的value是运行结果,done表示是否运行完成。2. throw( )方法throw()方法在函数体外抛出一个错误,然后在函数体内捕获。function *gen1() { try{ yield; } catch(e) { console.log(‘内部捕获’) }}let g1 = gen1();g1.next();g1.throw(new Error());3. return( )方法return()方法返回给定值,并终结生成器,在return后面的yield不会再被执行。function *gen2(){ yield 1; yield 2; yield 3;}let g2 = gen2();g2.next(); // { value:1, done:false }g2.return(); // { value:undefined, done:true }g2.next(); // { value:undefined, done:true }5. Promise + async & await在ES2017中,提供了async / await两个关键字来实现异步,是异步编程的最高境界,就是根本不用关心它是否是异步,很多人认为它是异步编程的终极解决方案。async / await寄生于Promise,本质上还是基于Generator函数,可以说是Generator函数的语法糖,async用于申明一个function是异步的,而await可以认为是async wait的简写,等待一个异步方法执行完成。async function demo() { let result = await Promise.resolve(123); console.log(result);}demo();async函数返回的是一个Promise对象,在上述例子中,表示demo是一个async函数,await只能用在async函数里面,表示等待Promise返回结果后,再继续执行,await后面应该跟着Promise对象(当然,跟着其他返回值也没关系,只是会立即执行,这样就没有意义了)。Promise虽然一方面解决了callback的回调地狱,但是相对的把回调 “纵向发展” 了,形成了一个回调链:function sleep(wait) { return new Promise((res,rej) => { setTimeout(() => { res(wait); },wait); });}/let p1 = sleep(100);let p2 = sleep(200);let p =/sleep(100).then(result => { return sleep(result + 100);}).then(result02 => { return sleep(result02 + 100);}).then(result03 => { console.log(result03);})将上述代码改成async/await写法:async function demo() { let result01 = await sleep(100); //上一个await执行之后才会执行下一句 let result02 = await sleep(result01 + 100); let result03 = await sleep(result02 + 100); // console.log(result03); return result03;}demo().then(result => { console.log(result);});因为async返回的也是promise对象,所以用then接收就行了。如果是reject状态,可以用try-catch捕捉:let p = new Promise((resolve,reject) => { setTimeout(() => { reject(’error’); },1000);});async function demo(params) { try { let result = await p; } catch(e) { console.log(e); }}demo();这是基本的错误处理,但是当内部出现一些错误时,和Promise有点类似,demo()函数不会报错,还是需要catch回调捕捉,这就是内部的错误被 “静默” 处理了。let p = new Promise((resolve,reject) => { setTimeout(() => { reject(’error’); },1000);});async function demo(params) { // try { let result = name; // } catch(e) { // console.log(e); // }}demo().catch((err) => { console.log(err);})最后,总结一下JavaScript实现异步的5种方式的优缺点:回调函数:写起来方便,但是过多的回调会产生回调地狱,代码横向扩展,不易于维护和理解。发布订阅模式:方便管理和修改事件,不同的事件对应不同的回调,但是容易产生一些命名冲突的问题,事件到处触发,可能代码可读性不好。Promise对象:通过then方法来替代掉回调,解决了回调产生的参数不容易确定的问题,但是相对的把回调 “纵向发展” 了,形成了一个回调链。Generator函数:确实很好的解决了JavaScript中异步的问题,但是得依赖执行器函数。async/await:这可能是javascript中,解决异步的最好的方式了,让异步代码写起来跟同步代码一样,可读性和维护性都上来了。 ...

April 9, 2019 · 4 min · jiezi

promise 实现(es6 完整源码)

概览const PENDING = Symbol(‘PENDING’);const FULFILLED = Symbol(‘FULFILLED’);const REJECTED = Symbol(‘REJECTED’);class MyPromise { constructor(fn) {} then(successFn, failFn) {} catch(failFn) {} finally(finalFn){} static resolve(val) {} static reject(val) {} static all(promiseArr) {} static race(promiseArr) {}}Promise 内部维护着三种状态 pending、fulfilled 和 rejected,状态只能从 pending 转变到 fulfilled,或从 pending 转变到 rejected 且该转变不可逆。Promise 主要提供了三个实例方法 then,catch,finally,和4个静态方法 resolve,reject,all,race。所有方法都都返回一个Promise对象。构造函数 constructor(fn) { this.fulfilledQueue = []; this.rejectedQueue = []; this._status = PENDING; this._value = null; // 执行成功队列中的回调函数 const handleFulfilledQueue = () => { while(this.fulfilledQueue.length) { let fulfiledFn = this.fulfilledQueue.shift(); fulfiledFn(this._value); }; }; // 执行失败队列中的回调函数 const handleRejectedQueue = () => { while(this.rejectedQueue.length) { let rejectedFn = this.rejectedQueue.shift(); rejectedFn(this._value); }; }; // 完成状态转变,执行回调队列中的回调函数 const _resolve = (val) => { const fn = () => { if(this._status !== PENDING) { return; } if(val instanceof MyPromise) { val.then((res) => { this._status = FULFILLED; this._value = res; handleFulfilledQueue(); }, (err) => { this._status = REJECTED; this._value = err; handleRejectedQueue(); }); } else { this._status = FULFILLED; this._value = val; handleFulfilledQueue(); } } // 保证promise 回调函数一定是在同步任务之后执行; setTimeout(fn, 0); } // 完成状态Pending到REJECTED的转变,执行rejected队列中的回调函数 const _reject = (val) => { const fn = () => { if(this._status !== PENDING) { return; } this._status = REJECTED; this._value = val; handleRejectedQueue(); } setTimeout(fn, 0); } try { // 处理外部传入函数执行异常 fn(_resolve, _reject); } catch(e) { return _reject(e); } }Promise 构造函数接收一个函数执行器作为参数,该执行器的两个参数 _resolve、_reject均为函数类型,由 Promise 内部实现。执行器在 Promise 构造函数中被立即执行。注意: MyPromise 使用 Timeout 实现异步,使得 MyPromise 只能添加 macrotask,实际上原生的Promise 是 microtaskthen 方法 then(successFn, failFn) { return new MyPromise((resolve, reject) => { // 执行成功时的回调函数 const handleSucess = (fn) => { try { if(typeof fn === ‘function’) { const res = fn(this._value); if(res instanceof MyPromise) { res.then(resolve, reject); } else { resolve(res); } } else { resolve(this._value) } } catch(e){ reject(e); } } // 执行失败时的回调函数 const handleFail = (fn) => { try { if(typeof fn === ‘function’) { const res = fn(this._value); if(res instanceof MyPromise) { res.then(resolve, reject); } else { resolve(res); } } else { reject(this._value); } } catch(e) { reject(e); } } switch(this._status){ case PENDING: // 异步任务尚未完成,将回调函数推入相应队列 this.fulfilledQueue.push(() => { handleSucess(successFn); }); this.rejectedQueue.push(() => { handleFail(failFn); }); break; case FULFILLED: // 异步任务成功完成,执行成功回调函数 handleSucess(successFn); break; case REJECTED: // 异步任务已失败,执行失败回调函数 handleFail(failFn); break; default: console.log(‘Promise error status:’, this._status); break; }; }); }then 方法是 Promise 的一个主要方法,catch 和 finally 都可以用 then 来实现。当 Promise 的状态已经流转时,回调函数会立即被执行,当 Promise 还处于 Pending 状态时,回调函数被推入相应队列中等待执行。完整代码class MyPromise { constructor(fn) { this.fulfilledQueue = []; this.rejectedQueue = []; this._status = PENDING; this._value = null; const handleFulfilledQueue = () => { while(this.fulfilledQueue.length) { let fulfiledFn = this.fulfilledQueue.shift(); fulfiledFn(this._value); }; }; const handleRejectedQueue = () => { console.log(this.rejectedQueue); while(this.rejectedQueue.length) { let rejectedFn = this.rejectedQueue.shift(); rejectedFn(this._value); }; }; // 完成状态转变,执行回调队列中的回调函数 const _resolve = (val) => { const fn = () => { if(this._status !== PENDING) { return; } if(val instanceof MyPromise) { val.then((res) => { this._status = FULFILLED; this._value = res; handleFulfilledQueue(); }, (err) => { this._status = REJECTED; this._value = err; handleRejectedQueue(); }); } else { this._status = FULFILLED; this._value = val; handleFulfilledQueue(); } } setTimeout(fn, 0); } // 完成状态Pending到REJECTED的转变,执行rejected队列中的回调函数 const _reject = (val) => { const fn = () => { if(this._status !== PENDING) { return; } this._status = REJECTED; this._value = val; handleRejectedQueue(); } setTimeout(fn, 0); } try { // 处理外部传入函数执行异常 fn(_resolve, _reject); } catch(e) { return _reject(e); } } then(successFn, failFn) { return new MyPromise((resolve, reject) => { // 执行成功时的回调函数 const handleSucess = (fn) => { try { if(typeof fn === ‘function’) { const res = fn(this._value); if(res instanceof MyPromise) { res.then(resolve, reject); } else { resolve(res); } } else { resolve(this._value) } } catch(e){ reject(e); } } // 执行失败时的回调函数 const handleFail = (fn) => { try { if(typeof fn === ‘function’) { const res = fn(this._value); if(res instanceof MyPromise) { res.then(resolve, reject); } else { resolve(res); } } else { reject(this._value); } } catch(e) { reject(e); } } switch(this._status){ case PENDING: // 异步任务尚未完成,将回调函数推入相应队列 this.fulfilledQueue.push(() => { handleSucess(successFn); }); this.rejectedQueue.push(() => { handleFail(failFn); }); break; case FULFILLED: // 异步任务成功完成,执行成功回调函数 handleSucess(successFn); break; case REJECTED: // 异步任务已失败,执行失败回调函数 handleFail(failFn); break; default: console.log(‘Promise error status:’, this._status); break; }; }); } catch(failFn) { return this.then(null, failFn); } finally(finalFn){ return this.then(finalFn, finalFn); } static resolve(val) { if(val instanceof MyPromise) { return val; } else { return new MyPromise((resolve, reject) =>{ resolve(val); }); } } static reject(val) { return new MyPromise((resolve, reject) => { reject(val); }); } static all(promiseArr) { return new Promise((resolve, reject) =>{ const len = promiseArr.length; let count = 0; let result = []; for(let i = 0; i < len; i++) { promiseArr[i].then((val) => { count++; result.push[val]; if(count === len){ resolve(result); } }, (err) => { reject(err); }); } }); } static race(promiseArr) { return new Promise((resolve, reject) =>{ const len = promiseArr.length; for(let i = 0; i < len; i++) { promiseArr[i].then((val) => { resolve(val); }, (err) => { reject(err); }); } }); }} ...

April 5, 2019 · 4 min · jiezi

Promise 对象的理解

Promise 含义Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。Promise 对象有以下两个特点:对象的状态不受外界影响。有三种状态,分别为 pending(进行中)、fulfilled(已成功)和 rejected(已失败)。一旦状态改变,就不会再变,任何时候都可以得到这个结果。状态改变只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。使用 Promise 对象的好处在于:可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。Promise 对象提供统一的接口,使得控制异步操作更加容易。Promise 缺点:无法取消 Promise,一旦新建它就会立即执行,无法中途取消。如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。基本用法ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。const promise = new Promise((resolve, reject) => { setTimeout(() => { const num = Math.random(); if (num > 0.5) { resolve(num); } else { reject(num); } }, 500);});promise.then( res => { console.log(“成功:” + res); }, err => { console.log(“失败:” + err); });Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。resolve 函数的作用:将 Promise 对象的状态从“未完成(pending)”变为“成功(resolved)”,在异步操作成功时调用,并将异步操作的结果作为参数传递出去。reject 函数的作用:将 Promise 对象的状态从“未完成(pending)”变为“失败(rejected)”在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。then 方法作用:接受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为 resolved 时调用,第二个回调函数是 Promise 对象的状态变为 rejected 时调用。第二个函数可选,不一定要提供,也可以将第二个函数作为 catch 方法的参数。catch 方法作用:用于指定发生错误时的回调函数。Promise 对象异步操作抛出错误,状态就会变为 rejected,就会调用 catch 方法指定的回调函数处理这个错误。另外,then 方法指定的回调函数,如果运行中抛出错误,也会被 catch 方法捕获。const promise = new Promise((resolve, reject) => { setTimeout(() => { const num = Math.random(); if (num > 0.5) { resolve(num); } else { reject(num); } }, 500);});promise .then(res => { console.log(“成功:” + res); }) .catch(err => { console.log(“失败:” + err); });promise .then(res => { console.log(“成功:” + res); throw new Error(“test”); }) .catch(err => { // num > 0.5时打印 “失败:Error: test” console.log(“失败:” + err); });Promise 执行顺序Promise 新建后立即执行,then 方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,catch 同理。调用 resolve 或 reject 并不会终结 Promise 的参数函数的执行。const promise = new Promise((resolve, reject) => { console.log(“我是第一个执行的”); resolve();});promise.then(res => { console.log(“我是第三个执行的”);});console.log(“我是第二个执行的”);resolve 函数和 reject 函数的参数reject 函数的参数通常是 Error 对象的实例,表示抛出的错误;resolve 函数的参数除了正常的值以外,还可能是另一个 Promise 实例。如果一个 Promise(P2) 的 resolve 参数是另一个 Promise(P1),此时 P1 的状态就会传给 P2,P1 的状态决定了 P2 的状态,P1 的状态改变,P2 的回调函数才会执行。const p1 = new Promise(function(resolve, reject) { setTimeout(() => reject(new Error(“fail”)), 3000);});const p2 = new Promise(function(resolve, reject) { setTimeout(() => resolve(p1), 1000);});p2.then(result => console.log(result)).catch(error => console.log(error));// Error: fail上面代码中,p1 是一个 Promise,3 秒之后变为 rejected。p2 的状态在 1 秒之后改变,resolve 方法返回的是 p1。由于 p2 返回的是另一个 Promise,导致 p2 自己的状态无效了,由 p1 的状态决定 p2 的状态。所以,后面的 then 语句都变成针对后者(p1)。又过了 2 秒,p1 变为 rejected,导致触发 catch 方法指定的回调函数。Promise 链式调用then 方法可以返回一个新的 Promise 实例(注意,不是原来那个 Promise 实例)。因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法。const promise = new Promise((resolve, reject) => { resolve(“promise”);}) .then(res => { console.log(res); // promise return “promise1”; }) .then(res => { console.log(res); // promise1 return “promise2”; }) .then(res => { console.log(res); // promise2 });注意:只要一个 Promise 中抛出错误,将执行 catch 方法,then 链终止。const promise = new Promise((resolve, reject) => { resolve(“promise”);}) .then(res => { console.log(res); // promise throw new Error(“中止”); return “promise1”; }) .then(res => { console.log(res); return “promise2”; }) .then(res => { console.log(res); }) .catch(err => { console.log(err); // Error: 中止 });主动终止 then 链,通过 catch 方法来中止 promise chainconst promise = new Promise((resolve, reject) => { resolve(“promise”);}) .then(res => { console.log(res); // promise return Promise.reject({ notRealPromiseException: true }); }) .then(res => { console.log(res); return “promise2”; }) .then(res => { console.log(res); }) .catch(err => { if (err.notRealPromiseException) { return true; } console.log(err); });Promise.prototype.finally()finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。finally 本质上是 then 方法的特例,不接受任何参数,不依赖于 Promise 的执行结果promise.finally(() => { // 语句});// 等同于promise.then( result => { // 语句 return result; }, error => { // 语句 throw error; });Promise.all()Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。const promise = Promise.all([promise1, promise2, promise3])Promise.all 方法接受一个数组作为参数,promise、pro 米色、promise3 都是 Promise 实例,如果不是,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。(Promise.all 方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。了解 Iterator 接口)promise 的状态由 promise1、promise2、promise3 决定,分成两种情况。只有 promise1、promise2、promise3 的状态都变成 fulfilled,p 的状态才会变成 fulfilled,此时 promise1、promise2、promise3 的返回值组成一个数组,传递给 p 的回调函数。只要 promise1、promise2、promise3 之中有一个被 rejected,promise 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 promise 的回调函数。const p1 = new Promise((resolve, reject) => { resolve(“hello”);}) .then(result => result) .catch(e => e);const p2 = new Promise((resolve, reject) => { throw new Error(“报错了”);}) .then(result => result) .catch(e => e);Promise.all([p1, p2]) .then(result => console.log(result)) .catch(e => console.log(e));// [“hello”, Error: 报错了]上面代码中,p1 会 resolved,p2 首先会 rejected,但是 p2 有自己的 catch 方法,该方法返回的是一个新的 Promise 实例,p2 指向的实际上是这个实例。该实例执行完 catch 方法后,也会变成 resolved,导致 Promise.all()方法参数里面的两个实例都会 resolved,因此会调用 then 方法指定的回调函数,而不会调用 catch 方法指定的回调函数。如果 p2 没有自己的 catch 方法,就会调用 Promise.all()的 catch 方法。Promise.race()Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。const p = Promise.race([p1, p2, p3])只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。Promise.race 方法的参数与 Promise.all 方法一样,如果不是 Promise 实例,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。Promise.resolve()将现有对象转化为 Promise 对象。const promise = Promise.resolve(‘Hello world’)参数是 Promise 实例,该方法不做任何改变。参数是一个 thenable 对象,先将对象转为 Promise 对象,然后立即执行 thenable 方法。相当于将 thenable 对象中的 then 方法处理的值作为参数传给 promise then 方法。let thenObj = { then(resolve, reject) { resolve(“Hello”); }};const promise = Promise.resolve(thenObj);promise.then(res => { console.log(res); // Hello});参数不是具有 then 方法的对象,或根本就不是对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为 resolved。Promise.reject()Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为 rejected。 ...

April 4, 2019 · 3 min · jiezi

如何正确的使用Promise

promise用法对比传统回调函数与Pormise的写法传统回调函数// 声明函数function run(callback) { let parmas = 0; if (callback) callback(parmas);};function fnStep1(callback) { let parmas = 123; if (callback) callback(parmas);};function fnStep2(callback) { let parmas = 456; if (callback) callback(parmas);};function fnStep3(callback) { let parmas = 789; if (callback) callback(parmas);};// fnStep4 … // 传统使用回调的写法run(function (parmas) { // parmas = 0 console.log(parmas); fnStep1(function (parmas1) { // parmas = 123 console.log(parmas1); fnStep2(function (parmas2) { // parmas = 456 console.log(parmas2); fnStep3(function (parmas3) { // … // 一直嵌套 }); }); });});Promise的写法let p = new Promise((resolve, reject) => { // ?异步操作,最终调用: // const parmas = 0; resolve(parmas); // fulfilled // ?或 // reject(“failure reason”); // rejected})p .then( (parmas) => { // parmas,resolve返回的值 console.log(parmas); // 你的代码块 code… return 123; //返回值给下一个then } ) .then( (parmas) => { // parmas,上一个then返回的值 console.log(parmas); // 你的代码块 code… return 456; //返回值给下一个then } ) .then( (parmas) => { // parmas,上一个then返回的值 console.log(parmas); // 你的代码块 code… return 789; //返回值给下一个then } )Promise异步回调Promise要比传统回调函数更简洁直观,可读性更强。那如何使用Promise进行异步回调? 如何捕获错误?// 声明函数function asyncFn(a) { return new Promise((resolve, reject) => { a += 1; setTimeout(function () { // 使用resolve则返回a resolve(a); // 使用reject则返回错误,并结束then的继续向下执行,并会跳到catch // reject(new Error(“fail”)); }, 2000); });}// 执行asyncFn(1).then( (a) => { // 过了2秒后接收到a值 => 2 console.log(a); const newVal = 5; // const newVal = {a: 5}; // const newVal = new Promise((resolve, reject) =>{}); // 返回值可以是数字,字串,对象或者是 Promise return newVal; }).then( (newVal) => { // newVal 获得上一个then返回的值 或 返回的Promise的返回值 }).catch( (err)=>{ // 如用到reject,则会直接跳到此处 console.log(err) }); ...

April 2, 2019 · 2 min · jiezi

手把手教你实现一个Promise

1、constructor首先我们都知道Promise 有三个状态,为了方便我们把它定义成常量const PENDING = ‘pending’;const FULFILLED = ‘fulfilled’;const REJECTED = ‘rejected’;接下来我们来定义一个类class MyPromise { constructor(executor) { //控制状态,使用了一次之后,接下来的都不被使用 this.state = PENDING; this.value = null; this.reason = null; // 定义resolve函数 const resolve = value => { if (this.state === PENDING) { this.value = value; this.state = FULFILLED; } } // 定义reject函数 const reject = value => { if (this.state === PENDING) { this.reason = value; this.state = REJECTED; } } // executor方法可能会抛出异常,需要捕获 try { // 将resolve和reject函数给使用者 executor(resolve, reject); } catch (error) { // 如果在函数中抛出异常则将它注入reject中 reject(error); } }}到这基本比较好理解我简单说明一下executor:这是实例Promise对象时在构造器中传入的参数,一般是一个function(resolve,reject){}state:Promise的状态,一开始是默认的pendding状态,每当调用道resolve和reject方法时,就会改变其值,在后面的then方法中会用到value:resolve回调成功后,调用resolve方法里面的参数值reason:reject回调成功后,调用reject方法里面的参数值resolve:声明resolve方法在构造器内,通过传入的executor方法传入其中,用以给使用者回调reject:声明reject方法在构造器内,通过传入的executor方法传入其中,用以给使用者回调2、thenthen就是将Promise中的resolve或者reject的结果拿到,那么我们就能知道这里的then方法需要两个参数,成功回调和失败回调,上代码!then(onFulfilled, onRejected) { if (this.state === FULFILLED) { onFulfilled(this.value) } if (this.state === REJECTED) { onRejected(this.reason) }}我们来简单的运行一下测试代码const mp = new MyPromise((resolve, reject)=> { resolve(’******* i love you ’);})mp.then((suc)=> {console.log(11111, suc);}, (err)=> {console.log(’ 你不爱我了******’, err)})// 11111 ‘******* i love you ‘这样看着好像没有问题,那么我们来试试异步函数呢?const mp = new MyPromise((resolve, reject)=> { setTimeout(()=> { resolve(’ i love you ’); }, 0)})mp.then((suc)=> {console.log(11111, suc);}, (err)=> {console.log(’ 你不爱我了******’, err)})我们会发现什么也没有打印,哪里出问题了呢?原来是由于异步的原因,当我们执行到then的时候this. state的值还没发生改变,所以then里面的判断就失效了。那么我们该怎么解决呢?这就要说回经典得callback 了。来上源码// 存放成功回调的函数this.onFulfilledCallbacks = [];// 存放失败回调的函数this.onRejectedCallbacks = [];const resolve = value => { if (this.state === PENDING) { this.value = value; this.state = FULFILLED; this.onFulfilledCallbacks.map(fn => fn()); }}const reject = value => { if (this.state === PENDING) { this.value = value; this.reason = REJECTED; this.onRejectedCallbacks.map(fn => fn()); }}在then里面添加then(onFulfilled, onRejected) { // … if(this.state === PENDING) { this.onFulfilledCallbacks.push(()=> { onFulfilled(this.value); }); this.onRejectedCallbacks.push(()=> { onRejected(this.value); }) }}好了,到这异步的问题解决了,我们再来执行一下刚才的测试代码。结果就出来了。到这我们还缺什么呢?链式调用当我们不传参数时应当什么运行这二个的思路也都很简单,链式调用也就是说我们再返回一个promise的实例就好了。而不传参则就是默认值的问题了。下面来看源码then(onFulfilled, onRejected) { let self = this; let promise2 = null; //解决onFulfilled,onRejected没有传值的问题 onFulfilled = typeof onFulfilled === ‘function’ ? onFulfilled : y => y //因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后then的resolve中捕获 onRejected = typeof onRejected === ‘function’ ? onRejected : err => { throw err; } promise2 = new MyPromise((resolve, reject) => { if (self.state === PENDING) { console.log(’then PENDING’) self.onFulfilledCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(self.value); console.log(333333, x, typeof x); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }, 0) }); self.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(self.reason); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }, 0); }); } if (self.state === FULFILLED) { console.log(’then FULFILLED’) setTimeout(() => { try { let x = onFulfilled(self.value); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }, 0); } if (self.state === REJECTED) { console.log(’then REJECTED’) setTimeout(() => { try { let x = onRejected(self.reason); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }) } }); return promise2;}为什么外面要包一层setTimeout?:因为Promise本身是一个异步方法,属于微任务一列,必须得在执行栈执行完了在去取他的值,所以所有的返回值都得包一层异步setTimeout。resolvePromise是什么?:这其实是官方Promise/A+的需求。因为你的then可以返回任何职,当然包括Promise对象,而如果是Promise对象,我们就需要将他拆解,直到它不是一个Promise对象,取其中的值。3、resolvePromise我们直接看代码resolvePromise(promise2, x, resolve, reject) { let self = this; let called = false; // called 防止多次调用 //因为promise2是上一个promise.then后的返回结果,所以如果相同,会导致下面的.then会是同一个promise2,一直都是,没有尽头 //相当于promise.then之后return了自己,因为then会等待return后的promise,导致自己等待自己,一直处于等待 if (promise2 === x) { return reject(new TypeError(‘循环引用’)); } //如果x不是null,是对象或者方法 if (x !== null && (Object.prototype.toString.call(x) === ‘[object Object]’ || Object.prototype.toString.call(x) === ‘[object Function]’)) { // x是对象或者函数 try { let then = x.then; if (typeof then === ‘function’) { then.call(x, (y) => { // 别人的Promise的then方法可能设置了getter等,使用called防止多次调用then方法 if (called) return; called = true; // 成功值y有可能还是promise或者是具有then方法等,再次resolvePromise,直到成功值为基本类型或者非thenable self.resolvePromise(promise2, y, resolve, reject); }, (reason) => { if (called) return; called = true; reject(reason); }); } else { if (called) return; called = true; resolve(x); } } catch (reason) { if (called) return; called = true; reject(reason); } } else { // x是普通值,直接resolve resolve(x); }}为什么要在一开始判断promise2和x?:首先在Promise/A+中写了需要判断这两者如果相等,需要抛出异常,我就来解释一下为什么,如果这两者相等,我们可以看下下面的例子,第一次p2是p1.then出来的结果是个Promise对象,这个Promise对象在被创建的时候调用了resolvePromise(promise2,x,resolve,reject)函数,又因为x等于其本身,是个Promise,就需要then方法递归它,直到他不是Promise对象,但是x(p2)的结果还在等待,他却想执行自己的then方法,就会导致等待。为什么要递归去调用resolvePromise函数?:相信细心的人已经发现了,我这里使用了递归调用法,首先这是Promise/A+中要求的,其次是业务场景的需求,当我们碰到那种Promise的resolve里的Promise的resolve里又包了一个Promise的话,就需要递归取值,直到x不是Promise对象。4、catch//catch方法catch(onRejected){ return this.then(null,onRejected)}5、finallyfinally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。finally(fn) { return this.then(value => { fn(); return value; }, reason => { fn(); throw reason; });};6、resolve/reject大家一定都看到过Promise.resolve()、Promise.reject()这两种用法,它们的作用其实就是返回一个Promise对象,我们来实现一下。static resolve(val) { return new MyPromise((resolve, reject) => { resolve(val) })}//reject方法static reject(val) { return new MyPromise((resolve, reject) => { reject(val) })}7、allall方法可以说是Promise中很常用的方法了,它的作用就是将一个数组的Promise对象放在其中,当全部resolve的时候就会执行then方法,当有一个reject的时候就会执行catch,并且他们的结果也是按着数组中的顺序来排放的,那么我们来实现一下。static all(promiseArr) { return new MyPromise((resolve, reject) => { let result = []; promiseArr.forEach((promise, index) => { promise.then((value) => { result[index] = value; if (result.length === promiseArr.length) { resolve(result); } }, reject); }); });}8、racerace方法虽然不常用,但是在Promise方法中也是一个能用得上的方法,它的作用是将一个Promise数组放入race中,哪个先执行完,race就直接执行完,并从then中取值。我们来实现一下吧。static race(promiseArr) { return new MyPromise((resolve, reject) => { promiseArr.forEach(promise => { promise.then((value) => { resolve(value); }, reject); }); });}9、deferredstatic deferred() { let dfd = {}; dfd.promies = new MyPromise((resolve, reject) => { dfd.resolve = resolve; dfd.rfeject = reject; }); return dfd;};什么作用呢?看下面代码你就知道了let fs = require(‘fs’)let MyPromise = require(’./MyPromise’)//Promise上的语法糖,为了防止嵌套,方便调用//坏处 错误处理不方便function read(){ let defer = MyPromise.defer() fs.readFile(’./1.txt’,‘utf8’,(err,data)=>{ if(err)defer.reject(err) defer.resolve(data) }) return defer.Promise}10、测试const mp1 = MyPromise.resolve(1);const mp2 = MyPromise.resolve(2);const mp3 = MyPromise.resolve(3);const mp4 = MyPromise.reject(4);MyPromise.all([mp1, mp2, mp3]).then(x => { console.log(x);}, (err) => { console.log(’err1’, err);})MyPromise.race([mp1, mp4, mp2, mp3]).then(x => { console.log(x);}, (err) => { console.log(’err2’, err);})var mp = new MyPromise((resolve, reject) => { console.log(11111); setTimeout(() => { resolve(22222); console.log(3333); }, 1000);});mp.then(x => { console.log(x);}, (err) => { console.log(’err2’, err);})//11111//[ 1, 2, 3 ]//1//3333//22222完整源码请查看如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。 ...

March 29, 2019 · 4 min · jiezi

ES6--浅析Promise内部结构

一、前言什么是promise?promsie的核心是什么?promise如何解决回调地狱的?等问题1、什么是promise?promise是表示异步操作的最终结果;可以用来解决回调地狱和并发IO操作的问题A promise represents the eventual result of an asynchronous operation.2、promise 的核心是什么?promise的核心就是链式调用3、采用什么方法可以实现链式调用?通过使用then的方法,then方法是用来注册在这个Promise状态确定后的回调,很明显,then方法需要写在原型链上。4、promise是如何解决回调地狱的问题?(1)如果一个promise返回的是一个promise,会把这个promise传递结果传递到下一次的then中;(2)如果一个promise返回的是一个普通的值,会把这个普通值作为下一次then的成功回调结果;(3)如果当前promise失败了,会走下一个then的回调函数;(4)如果then不返回值,就会有一个默认值为undefined,作为普通值,会作为下一个then的成功回调;(5)catch是错误没有处理的情况才会执行;(6)then中可以不写东西二、promise的标准解读1、只有一个then方法,没有catch,race,all等方法,甚至没有构造函数;Promise标准中仅指定了Promise对象的then方法的行为,其它一切我们常见的方法/函数都并没有指定,包括catch,race,all等常用方法,甚至也没有指定该如何构造出一个Promise对象,另外then也没有一般实现中(Q, $q等)所支持的第三个参数,一般称onProgress2、then方法返回一个新的Promise;Promise的then方法返回一个新的Promise,而不是返回this,此处在下文会有更多解释promise2 = promise1.then(alert)promise2 != promise1 // true3、不同Promise的实现需要可以相互调用(interoperable)4、Promise的初始状态为pending,它可以由此状态转换为fulfilled(本文为了一致把此状态叫做resolved)或者rejected,一旦状态确定,就不可以再次转换为其它状态,状态确定的过程称为settle三、实现一个promise1、构造函数因为标准并没有指定如何构造一个Promise对象,所以我们同样以目前一般Promise实现中通用的方法来构造一个Promise对象,也是ES6原生Promise里所使用的方式,即:// Promise构造函数接收一个executor函数,executor函数执行完同步或异步操作后,调用它的两个参数resolve和rejectvar promise = new Promise(function(resolve, reject) { /* 如果操作成功,调用resolve并传入value 如果操作失败,调用reject并传入reason */})我们先实现构造函数的框架如下:function Promise(executor) { var self = this self.status = ‘pending’ // Promise当前的状态 self.data = undefined // Promise的值 self.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面 self.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面 executor(resolve, reject) // 执行executor并传入相应的参数}上面的代码基本实现了Promise构造函数的主体,但目前还有两个问题:1、我们给executor函数传了两个参数:resolve和reject,这两个参数目前还没有定义2、executor有可能会出错(throw),类似下面这样,而如果executor出错,Promise应该被其throw出的值reject:new Promise(function(resolve, reject) { throw 2})所以我们需要在构造函数里定义resolve和reject这两个函数:function Promise(executor) { var self = this self.status = ‘pending’ // Promise当前的状态 self.data = undefined // Promise的值 self.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面 self.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面 function resolve(value) { // TODO } function reject(reason) { // TODO } try { // 考虑到执行executor的过程中有可能出错,所以我们用try/catch块给包起来,并且在出错后以catch到的值reject掉这个Promise executor(resolve, reject) // 执行executor } catch(e) { reject(e) }}有人可能会问,resolve和reject这两个函数能不能不定义在构造函数里呢?考虑到我们在executor函数里是以resolve(value),reject(reason)的形式调用的这两个函数,而不是以resolve.call(promise, value),reject.call(promise, reason)这种形式调用的,所以这两个函数在调用时的内部也必然有一个隐含的this,也就是说,要么这两个函数是经过bind后传给了executor,要么它们定义在构造函数的内部,使用self来访问所属的Promise对象。所以如果我们想把这两个函数定义在构造函数的外部,确实是可以这么写的:function resolve() { // TODO}function reject() { // TODO}function Promise(executor) { try { executor(resolve.bind(this), reject.bind(this)) } catch(e) { reject.bind(this)(e) }}但是众所周知,bind也会返回一个新的函数,这么一来还是相当于每个Promise对象都有一对属于自己的resolve和reject函数,就跟写在构造函数内部没什么区别了,所以我们就直接把这两个函数定义在构造函数里面了。不过话说回来,如果浏览器对bind的所优化,使用后一种形式应该可以提升一下内存使用效率。另外我们这里的实现并没有考虑隐藏this上的变量,这使得这个Promise的状态可以在executor函数外部被改变,在一个靠谱的实现里,构造出的Promise对象的状态和最终结果应当是无法从外部更改的。接下来,我们实现resolve和reject这两个函数function Promise(executor) { // … function resolve(value) { if (self.status === ‘pending’) { self.status = ‘resolved’ self.data = value for(var i = 0; i < self.onResolvedCallback.length; i++) { self.onResolvedCallbacki } } } function reject(reason) { if (self.status === ‘pending’) { self.status = ‘rejected’ self.data = reason for(var i = 0; i < self.onRejectedCallback.length; i++) { self.onRejectedCallbacki } } } // …}基本上就是在判断状态为pending之后把状态改为相应的值,并把对应的value和reason存在self的data属性上面,之后执行相应的回调函数,逻辑很简单,这里就不多解释了。2、then方法then方法是用来注册这个promise确定状态后的回调,then方法是需要写在原型链上。自然约束:then方法会返回一个Promise,关于这一点,Promise/A+标准并没有要求返回的这个Promise是一个新的对象,但在Promise/A标准中,明确规定了then要返回一个新的对象,目前的Promise实现中then几乎都是返回一个新的Promise(https://promisesaplus.com/dif…,所以在我们的实现中,也让then返回一个新的Promise对象。下面我们来实现then方法:// then方法接收两个参数,onResolved,onRejected,分别为Promise成功或失败后的回调Promise.prototype.then = function(onResolved, onRejected) { var self = this var promise2 // 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理 onResolved = typeof onResolved === ‘function’ ? onResolved : function(v) {} onRejected = typeof onRejected === ‘function’ ? onRejected : function(r) {} if (self.status === ‘resolved’) { return promise2 = new Promise(function(resolve, reject) { }) } if (self.status === ‘rejected’) { return promise2 = new Promise(function(resolve, reject) { }) } if (self.status === ‘pending’) { return promise2 = new Promise(function(resolve, reject) { }) }}Promise总共有三种可能的状态,我们分三个if块来处理,在里面分别都返回一个new Promise。根据标准,我们知道,对于如下代码,promise2的值取决于then里面函数的返回值:promise2 = promise1.then(function(value) { return 4}, function(reason) { throw new Error(‘sth went wrong’)})如果promise1被resolve了,promise2的将被4 resolve,如果promise1被reject了,promise2将被new Error(‘sth went wrong’) reject,更多复杂的情况不再详述。3、完整的promisetry { module.exports = Promise} catch (e) {}function Promise(executor) { var self = this self.status = ‘pending’ self.onResolvedCallback = [] self.onRejectedCallback = [] function resolve(value) { if (value instanceof Promise) { return value.then(resolve, reject) } setTimeout(function() { // 异步执行所有的回调函数 if (self.status === ‘pending’) { self.status = ‘resolved’ self.data = value for (var i = 0; i < self.onResolvedCallback.length; i++) { self.onResolvedCallbacki } } }) } function reject(reason) { setTimeout(function() { // 异步执行所有的回调函数 if (self.status === ‘pending’) { self.status = ‘rejected’ self.data = reason for (var i = 0; i < self.onRejectedCallback.length; i++) { self.onRejectedCallbacki } } }) } try { executor(resolve, reject) } catch (reason) { reject(reason) }}function resolvePromise(promise2, x, resolve, reject) { var then var thenCalledOrThrow = false if (promise2 === x) { return reject(new TypeError(‘Chaining cycle detected for promise!’)) } if (x instanceof Promise) { if (x.status === ‘pending’) { //because x could resolved by a Promise Object x.then(function(v) { resolvePromise(promise2, v, resolve, reject) }, reject) } else { //but if it is resolved, it will never resolved by a Promise Object but a static value; x.then(resolve, reject) } return } if ((x !== null) && ((typeof x === ‘object’) || (typeof x === ‘function’))) { try { then = x.then //because x.then could be a getter if (typeof then === ‘function’) { then.call(x, function rs(y) { if (thenCalledOrThrow) return thenCalledOrThrow = true return resolvePromise(promise2, y, resolve, reject) }, function rj(r) { if (thenCalledOrThrow) return thenCalledOrThrow = true return reject(r) }) } else { resolve(x) } } catch (e) { if (thenCalledOrThrow) return thenCalledOrThrow = true return reject(e) } } else { resolve(x) }}Promise.prototype.then = function(onResolved, onRejected) { var self = this var promise2 onResolved = typeof onResolved === ‘function’ ? onResolved : function(v) { return v } onRejected = typeof onRejected === ‘function’ ? onRejected : function(r) { throw r } if (self.status === ‘resolved’) { return promise2 = new Promise(function(resolve, reject) { setTimeout(function() { // 异步执行onResolved try { var x = onResolved(self.data) resolvePromise(promise2, x, resolve, reject) } catch (reason) { reject(reason) } }) }) } if (self.status === ‘rejected’) { return promise2 = new Promise(function(resolve, reject) { setTimeout(function() { // 异步执行onRejected try { var x = onRejected(self.data) resolvePromise(promise2, x, resolve, reject) } catch (reason) { reject(reason) } }) }) } if (self.status === ‘pending’) { // 这里之所以没有异步执行,是因为这些函数必然会被resolve或reject调用,而resolve或reject函数里的内容已是异步执行,构造函数里的定义 return promise2 = new Promise(function(resolve, reject) { self.onResolvedCallback.push(function(value) { try { var x = onResolved(value) resolvePromise(promise2, x, resolve, reject) } catch (r) { reject(r) } }) self.onRejectedCallback.push(function(reason) { try { var x = onRejected(reason) resolvePromise(promise2, x, resolve, reject) } catch (r) { reject(r) } }) }) }}Promise.prototype.catch = function(onRejected) { return this.then(null, onRejected)}Promise.deferred = Promise.defer = function() { var dfd = {} dfd.promise = new Promise(function(resolve, reject) { dfd.resolve = resolve dfd.reject = reject }) return dfd}四、promise的常用方法是如何实现1、Promise.resolve / Promise.reject 实现// 原生的Promise.resolve使用Promise.resolve(‘hello swr’).then((data)=>{ // 直接把成功的值传递给下一个then console.log(data) // hello swr})// 那么Promise.resolve内部是怎么实现的呢?Promise.resolve = function(value){ return new Promise((resolve,reject)=>{ // 在内部new一个Promise对象 resolve(value) })}// 同理,Promise.reject内部也是类似实现的Promise.reject = function(reason){ return new Promise((resolve,reject)=>{ reject(reason) })}2、catch的实现// 原生Promise的catch使用Promise.reject(‘hello swr’).catch((e)=>{ console.log(e) // hello swr})// 上面这段代码相当于下面这段代码Promise.reject(‘hello swr’).then(null,(e)=>{ // then里直接走了失败的回调 console.log(e) // hello swr})// 内部实现Promise.prototype.catch = function(onRejected){ return this.then(null,onRejected) // 相当于then里的成功回调只传个null}3、promise.all的实现同时执行多个异步,并且返回一个新的promise,成功的值是一个数组,该数组的成员的顺序是传参给promise.all的顺序// 原生Promise.all的使用// 假设1.txt内容为hello 2.txt内容为swrlet fs = require(‘fs’)function read(filePath,encoding){ return new Promise((resolve,reject)=>{ fs.readFile(filePath,encoding,(err,data)=>{ if(err) reject(err) resolve(data) }) })}Promise.all([read(’./1.txt’,‘utf8’),read(’./2.txt’,‘utf8’)]).then((data)=>{ console.log(data) // 全部读取成功后返回 [‘hello’,‘swr’] // 需要注意的是,当其中某个失败的话,则会走失败的回调函数})promise.all内部实现Promise.all = function(promises){ // promises 是一个数组 return new Promise((resolve,reject)=>{ let arr = [] let i = 0 function processData(index,data){ arr[index] = data // 5.我们能用arr.length === promises.length来判断请求是否全部完成吗? // 答案是不行的,假设arr[2] = ‘hello swr’ // 那么打印这个arr,将是[empty × 2, “hello swr”], // 此时数组长度也是为3,而数组arr[0] arr[1]则为空 // 那么换成以下的办法 if(++i === promises.length){ // 6.利用i自增来判断是否都成功执行 resolve(arr) // 此时arr 为[‘hello’,‘swr’] } } for(let i = 0;i < promises.length;i++){ // 1.在此处遍历执行 promises[i].then((data)=>{ // 2.data是成功后返回的结果 processData(i,data) // 4.因为Promise.all最终返回的是一个数组成员按照顺序排序的数组 // 而且异步执行,返回并不一定按照顺序 // 所以需要传当前的i },reject) // 3.如果其中有一个失败的话,则调用reject } })}4、promise.race方法实现,同时执行多个异步,然后那个快,就用那个的结果,race是赛跑// 原生Promise.race的使用// 一个成功就走成功的回调,一个失败就走失败的回调Promise.race([read(’./1.txt’,‘utf8’),read(’./2.txt’,‘utf8’)]).then((data)=>{ console.log(data) // 可能返回 ‘hello’ 也可能返回 ‘swr’ 看哪个返回快就用哪个作为结果})// 内部实现Promise.race = function(promises){ // promises 是一个数组 return new Promise((resolve,reject)=>{ for(let i = 0;i < promises.length;i++){ promises[i].then(resolve,reject) // 和上面Promise.all有点类似 } })}5、promise.defer = promise.deferred这个语法糖怎么理解呢?这个语法糖可以简化一些操作,比如:let fs = require(‘fs’)// 写法一:function read(filePath,encoding){ // 这里的new Promise依然是传递了一个executor回调函数 // 我们该怎样减少回调函数嵌套呢? return new Promise((resolve,reject)=>{ fs.readFile(filePath,encoding,(err,data)=>{ if(err) reject(err) resolve(data) }) })}// 写法二:// 这样的写法减少了一层回调函数的嵌套function read(filePath,encoding){ let dfd = Promise.defer() fs.readFile(filePath,encoding,(err,data)=>{ if(err) dfd.reject(err) dfd.resolve(data) }) return dfd.promise}read(’./1.txt’,‘utf8’).then((data)=>{ console.log(data)})五、promise的链式调用promise的核心在于:链式调用。promise主要解决两个问题:(1)回调地狱(2)并发的异步IO操作,同一时间内把这个结果拿到,比如有两个异步io操作,当这2个获取完毕后,才执行相应的代码1、回调地狱怎么解决那么我们来看下面的代码,并且改为promise。// 回调函数let fs = require(‘fs’)fs.readFile(’./a.txt’,‘utf8’,(err,data)=>{ // 往fs.readFile方法传递了第三个为函数的参数 if(err){ console.log(err) return } console.log(data)})// 改写为Promiselet fs = require(‘fs’)function read(filePath,encoding){ return new Promise((resolve,reject)=>{ fs.readFile(filePath,encoding,(err,data)=>{ if(err) reject(err) resolve() }) })}read(’./a.txt’,‘utf8’).then((data)=>{ // 在这里则不再需要传回调函数进去,而是采用then来达到链式调用 console.log(data)},(err)=>{ console.log(err)})这样看好像Promise也没什么优势,那么接下来我们对比一下// 假设有3个文件// - 1.txt 文本内容为'2.txt’// - 2.txt 文本内容为'3.txt’// - 3.txt 文本内容为’hello swr’// 用回调函数fs.readFile(’./1.txt’,‘utf8’,(err,data)=>{ fs.readFile(data,‘utf8’,(err,data)=>{ fs.readFile(data,‘utf8’,(err,data)=>{ console.log(data) // hello swr }) })})// 用Promiseread(’./1.txt’,‘utf8’).then((data)=>{ // 1.如果一个promise执行完后,返回的还是一个promise, // 会把这个promise的执行结果会传递给下一次then中 return read(data,‘utf8’)}).then((data)=>{ return read(data,‘utf8’)}).then((data)=>{ // 2.如果在then中返回的不是一个promise, // 而是一个普通值,会将这个普通值作为下次then的成功的结果 return data.split(’’).reverse().join(’’)}).then((data)=>{ console.log(data) // rws olleh // 3.如果当前then中失败了,会走下一个then的失败回调 throw new Error(‘出错’)}).then(null,(err)=>{ console.log(err) // Error:出错 报错了 // 4.如果在then中不返回值,虽然没有显式返回, // 但是默认是返回undefined,是属于普通值,依然会把这个普通值传到 // 下一个then的成功回调中}).then((data)=>{ console.log(data) // undefined})从上面可以看得出,改写为Promise的代码,更好阅读和维护,从用Promise方式可以得出结论:(1)如果一个promise执行完后,返回的是一个promise,会将这个promise的执行结果传递给下一个then回调成功中;(2)如果在then中返回的不是一个promise,而是一个普通的值,会将这个普通的值传到下一个then成功回调中;(3)如果当时then中失败了,会走下一个then的回调失败;(4)如果then不返回值,但是默认是返回undefined的,属于普通值,会将这个普通值传到下一个then成功回调中。如果在then中抛出错误,会怎么样呢?情况1:会被下一个then中的失败回调捕获// 情景一,会被下一个then中的失败回调捕获read(’./1.txt’,‘utf8’).then((data)=>{ throw new Error(‘出错了’)}).then(null,(err)=>{ console.log(err) // Error:出错了 报错})情况2:没有被失败回调捕获,抛出错误最终会变成异常read(’./1.txt’,‘utf8’).then((data)=>{ throw new Error(‘出错了’)})情况3:如果没有被失败的回调捕获,那么最终会被catch捕获到read(’./1.txt’,‘utf8’).then((data)=>{ throw new Error(‘出错了’)}).then((data)=>{ }).catch((err)=>{ console.log(err) // Error:出错了 报错})情况4:如果被失败的回调捕获,那么就不会被catch捕获到read(’./1.txt’,‘utf8’).then((data)=>{ throw new Error(‘出错了’)}).then(null,(err)=>{ console.log(err) // Error:出错了 报错}).catch((err)=>{ console.log(err) // 不会执行到这里})(5)catch是错误没有处理的情况下才会执行(6)then回调中可以不写六、关于promise的其他问题1、性能问题可能各位看官会觉得奇怪,Promise能有什么性能问题呢?并没有大量的计算啊,几乎都是处理逻辑的代码。理论上说,不能叫做“性能问题”,而只是有可能出现的延迟问题。什么意思呢,记得刚刚我们说需要把4块代码包在setTimeout里吧,先考虑如下代码:var start = +new Date()function foo() { setTimeout(function() { console.log(‘setTimeout’) if((+new Date) - start < 1000) { foo() } })}foo()运行上面的代码,会打印出多少次’setTimeout’呢,各位可以自己试一下,不出意外的话,应该是250次左右,我刚刚运行了一次,是241次。这说明,上述代码中两次setTimeout运行的时间间隔约是4ms(另外,setInterval也是一样的),实事上,这正是浏览器两次Event Loop之间的时间间隔,相关标准各位可以自行查阅。另外,在Node中,这个时间间隔跟浏览器不一样,经过我的测试,是1ms。单单一个4ms的延迟可能在一般的web应用中并不会有什么问题,但是考虑极端情况,我们有20个Promise链式调用,加上代码运行的时间,那么这个链式调用的第一行代码跟最后一行代码的运行很可能会超过100ms,如果这之间没有对UI有任何更新的话,虽然本质上没有什么性能问题,但可能会造成一定的卡顿或者闪烁,虽然在web应用中这种情形并不常见,但是在Node应用中,确实是有可能出现这样的case的,所以一个能够应用于生产环境的实现有必要把这个延迟消除掉。在Node中,我们可以调用process.nextTick或者setImmediate(Q就是这么做的),在浏览器中具体如何做,已经超出了本文的讨论范围,总的来说,就是我们需要实现一个函数,行为跟setTimeout一样,但它需要异步且尽早的调用所有已经加入队列的函数,这里有一个实现。2、如何停止一个promise链?在一些场景里,我们会遇到一个较长的promise的链式调用,在某一步出现的错误让我们没有必要去运行链式调用后面所有的代码,类似于下面这样的(此处省略then/catch里的函数):new Promise(function(resolve, reject) { resolve(42)}) .then(function(value) { // “Big ERROR!!!” }) .catch() .then() .then() .catch() .then()假设这个Big ERROR!!!的出现让我们完全没有必要运行后面所有的代码了,但链式调用的后面即有catch,也有then,无论我们是return还是throw,都不可避免的会进入某一个catch或then里面,那有没有办法让这个链式调用在Big ERROR!!!的后面就停掉,完全不去执行链式调用后面所有回调函数呢?从一个实现者的角度看问题时,确实找到了答案,就是在发生Big ERROR后return一个Promise,但这个Promise的executor函数什么也不做,这就意味着这个Promise将永远处于pending状态,由于then返回的Promise会直接取这个永远处于pending状态的Promise的状态,于是返回的这个Promise也将一直处于pending状态,后面的代码也就一直不会执行了,具体代码如下:new Promise(function(resolve, reject) { resolve(42)}) .then(function(value) { // “Big ERROR!!!” return new Promise(function(){}) }) .catch() .then() .then() .catch() .then()这种方式看起来有些山寨,它也确实解决了问题。但它引入的一个新问题就是链式调用后面的所有回调函数都无法被垃圾回收器回收(在一个靠谱的实现里,Promise应该在执行完所有回调后删除对所有回调函数的引用以让它们能被回收,在前文的实现里,为了减少复杂度,并没有做这种处理),但如果我们不使用匿名函数,而是使用函数定义或者函数变量的话,在需要多次执行的Promise链中,这些函数也都只有一份在内存中,不被回收也是可以接受的。将返回一个什么也不做的Promise封装成一个有语义的函数,以增加代码的可读性Promise.cancel = Promise.stop = function() { return new Promise(function(){})}这么使用了:new Promise(function(resolve, reject) { resolve(42)}) .then(function(value) { // “Big ERROR!!!” return Promise.stop() }) .catch() .then() .then() .catch() .then()3、promise的链上返回的最后一个promise出错了怎么办?new Promise(function(resolve) { resolve(42)}) .then(function(value) { alter(value) })但运行这段代码的话你会发现什么现象也不会发生,既不会alert出42,也不会在控制台报错,怎么回事呢。细看最后一行,alert被打成了alter,那为什么控制台也没有报错呢,因为alter所在的函数是被包在try/catch块里的,alter这个变量找不到就直接抛错了,这个错就正好成了then返回的Promise的rejection reason。解决办法:(1)所有的promise链的最后都加上一个catch,这样出错后就会被捕获到,这样违背了DRY原则,而且繁琐;(2)借鉴Q的一个方法done,把这个方法加到promise链的最后,就能够处理捕获最后一个promise出现的错误,其实个catch的思路一样,这个是框架来实现的。(3)在一个Promise被reject的时候检查这个Promise的onRejectedCallback数组,如果它为空,则说明它的错误将没有函数处理,这个时候,我们需要把错误输出到控制台,让开发者可以发现。在Promise被reject但又没有callback时,把错误输出到控制台。4、出错时,使用throw new Error()还是使用return Promise.reject(new Error())呢?从性能和编码的舒适角度考虑:(1)性能方面:throw new Error()会使代码进入catch块里的逻辑(我们把多有的回调都包在try/catch里),传说throw多了会影响性能,因为一旦throw,代码就有可能跳转到不可预知的位置。而使用promise.reject(new Error()),则需要构造一个新的promise对象(包含2个数组,4个函数:resolve/reject,onResolved/onRejected),也会花费一定的时间和内存。因为onResolved/onRejected函数是直接被包在promise实现里的try里,出错后直接进入到这个try对应的catch块,代码的跳跃幅度相对较小,性能应该可以忽略不记。(2)编码的舒适度方面:出错用throw,正常用return,正常可以明显的区分出错和正常综上觉得还是promise里发现显式错误后,用throw抛出错误比较好,而不是显式的构造一个呗reject的promise对象。七、实践注意:1、不要把promise写成嵌套的结构// 错误的写法promise1.then(function(value) { promise1.then(function(value) { promise1.then(function(value) { }) })})2、链式promise要返回一个promise,而不是构造一个promise// 错误的写法Promise.resolve(1).then(function(){ Promise.resolve(2)}).then(function(){ Promise.resolve(3)})八、参考https://github.com/xieranmaya… ...

March 26, 2019 · 6 min · jiezi

红绿灯效果

本实现来自 winter《重学前端》第16节 JavaScript执行(一): Promise里的代码为什么比setTimeout先执行?中的最后的问题,使用实现一个红绿灯效果。<body> <style> .redgreenlight{ width: 100px; height: 100px; margin: 0 auto; border-radius: 50%; line-height: 100px; text-align: center; background-color: #eee; border: double 3px gray; } .btn{ display: inline-block; background-color: #ddd; transition: transform 200ms linear; border-radius: 5px; } .btn:hover{ background-color: #888; } .btn:active{ transform: scale(.9); } </style> 打开电源,红绿灯开始工作 <div class=“btn” id=“btn” onclick=“lightopen()">启动</div> <div class=“redgreenlight” id=“light”>红绿灯</div> <script> // 延时的promise包装 function wait(delay){ return new Promise((resolve, reject) => { setTimeout(resolve, delay) }) } // 同步函数promise包装 function asyncDeal(asyncfn){ return new Promise((resolve, reject) => { asyncfn() resolve() }) } // 业务目标 // 绿色 3 // 黄色 1 // 红色 2 // 启动队列循环 function lightopen(){ btn.onclick = null // promise队列执行 return Promise.resolve() .then(() => asyncDeal(() => { light.style.backgroundColor = “#00ff00” light.innerText = (‘绿灯三秒’) })) .then(() => wait(3000)) .then(() => asyncDeal(() => { light.style.backgroundColor = “#ff8800” light.innerText = (‘黄灯一秒’) })) .then(() => wait(1000)) .then(() => asyncDeal(() => { light.style.backgroundColor = “#ff0000” light.innerText = (‘红灯俩秒’) })) .then(() => wait(2000)) .then(() => lightopen()) } </script> </body> ...

March 14, 2019 · 1 min · jiezi

Promise的源码实现(符合Promise/A+规范)

Promise的源码实现/** * 1. new Promise时,需要传递一个 executor 执行器,执行器立刻执行 * 2. executor 接受两个参数,分别是 resolve 和 reject * 3. promise 只能从 pending 到 rejected, 或者从 pending 到 fulfilled * 4. promise 的状态一旦确认,就不会再改变 * 5. promise 都有 then 方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, * 和 promise 失败的回调 onRejected * 6. 如果调用 then 时,promise已经成功,则执行 onFulfilled,并将promise的值作为参数传递进去。 * 如果promise已经失败,那么执行 onRejected, 并吧 promise 失败的原因作为参数传递进去。 * 如果promise的状态是pending,需要将onFulfilled和onRejected函数存放起来,等待状态确定后,再依次将对应的函数执行(发布订阅) * 7. then 的参数 onFulfilled 和 onRejected 可以缺省 * 8. promise 可以then多次,promise 的then 方法返回一个 promise * 9. 如果 then 返回的是一个结果,那么就会把这个结果作为参数,传递给下一个then的成功的回调(onFulfilled) * 10. 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个then的失败的回调(onRejected) * 11.如果 then 返回的是一个promise,那么需要等这个promise,那么会等这个promise执行完,promise如果成功, * 就走下一个then的成功,如果失败,就走下一个then的失败 */const PENDING = ‘pending’;const FULFILLED = ‘fulfilled’;const REJECTED = ‘rejected’;function Promise(executor) { let self = this; self.status = PENDING; self.onFulfilled = [];//成功的回调 self.onRejected = []; //失败的回调 //PromiseA+ 2.1 function resolve(value) { if (self.status === PENDING) { self.status = FULFILLED; self.value = value; self.onFulfilled.forEach(fn => fn());//PromiseA+ 2.2.6.1 } } function reject(reason) { if (self.status === PENDING) { self.status = REJECTED; self.reason = reason; self.onRejected.forEach(fn => fn());//PromiseA+ 2.2.6.2 } } try { executor(resolve, reject); } catch (e) { reject(e); }}Promise.prototype.then = function (onFulfilled, onRejected) { //PromiseA+ 2.2.1 / PromiseA+ 2.2.5 / PromiseA+ 2.2.7.3 / PromiseA+ 2.2.7.4 onFulfilled = typeof onFulfilled === ‘function’ ? onFulfilled : value => value; onRejected = typeof onRejected === ‘function’ ? onRejected : reason => { throw reason }; let self = this; //PromiseA+ 2.2.7 let promise2 = new Promise((resolve, reject) => { if (self.status === FULFILLED) { //PromiseA+ 2.2.2 //PromiseA+ 2.2.4 — setTimeout setTimeout(() => { try { //PromiseA+ 2.2.7.1 let x = onFulfilled(self.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { //PromiseA+ 2.2.7.2 reject(e); } }); } else if (self.status === REJECTED) { //PromiseA+ 2.2.3 setTimeout(() => { try { let x = onRejected(self.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); } else if (self.status === PENDING) { self.onFulfilled.push(() => { setTimeout(() => { try { let x = onFulfilled(self.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); }); self.onRejected.push(() => { setTimeout(() => { try { let x = onRejected(self.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); }); } }); return promise2;}function resolvePromise(promise2, x, resolve, reject) { let self = this; //PromiseA+ 2.3.1 if (promise2 === x) { reject(new TypeError(‘Chaining cycle’)); } if (x && typeof x === ‘object’ || typeof x === ‘function’) { let used; //PromiseA+2.3.3.3.3 只能调用一次 try { let then = x.then; if (typeof then === ‘function’) { //PromiseA+2.3.3 then.call(x, (y) => { //PromiseA+2.3.3.1 if (used) return; used = true; resolvePromise(promise2, y, resolve, reject); }, (r) => { //PromiseA+2.3.3.2 if (used) return; used = true; reject(r); }); }else{ //PromiseA+2.3.3.4 if (used) return; used = true; resolve(x); } } catch (e) { //PromiseA+ 2.3.3.2 if (used) return; used = true; reject(e); } } else { //PromiseA+ 2.3.3.4 resolve(x); }}module.exports = Promise;有专门的测试脚本可以测试所编写的代码是否符合PromiseA+的规范。首先,在promise实现的代码中,增加以下代码:Promise.defer = Promise.deferred = function () { let dfd = {}; dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd;}安装测试脚本:npm install -g promises-aplus-tests如果当前的promise源码的文件名为promise.js那么在对应的目录执行以下命令:promises-aplus-tests promise.jspromises-aplus-tests中共有872条测试用例。以上代码,可以完美通过所有用例。以下附上PromiseA+的规范(自己随便翻译了下)术语promise 是一个有then方法的对象或者是函数,行为遵循本规范thenable 是一个有then方法的对象或者是函数value 是promise状态成功时的值,包括 undefined/thenable或者是 promiseexception 是一个使用throw抛出的异常值reason 是promise状态失败时的值要求2.1 Promise StatesPromise 必须处于以下三个状态之一: pending, fulfilled 或者是 rejected2.1.1 如果promise在pending状态2.1.1.1 可以变成 fulfilled 或者是 rejected2.1.2 如果promise在fulfilled状态2.1.2.1 不会变成其它状态2.1.2.2 必须有一个value值2.1.3 如果promise在rejected状态2.1.3.1 不会变成其它状态2.1.3.2 必须有一个promise被reject的reason概括即是:promise的状态只能从pending变成fulfilled,或者从pending变成rejected.promise成功,有成功的value.promise失败的话,有失败的原因2.2 then方法promise必须提供一个then方法,来访问最终的结果promise的then方法接收两个参数promise.then(onFulfilled, onRejected)2.2.1 onFulfilled 和 onRejected 都是可选参数2.2.1.1 onFulfilled 必须是函数类型2.2.1.2 onRejected 必须是函数类型2.2.2 如果 onFulfilled 是函数:2.2.2.1 必须在promise变成 fulfilled 时,调用 onFulfilled,参数是promise的value2.2.2.2 在promise的状态不是 fulfilled 之前,不能调用2.2.2.3 onFulfilled 只能被调用一次2.2.3 如果 onRejected 是函数:2.2.3.1 必须在promise变成 rejected 时,调用 onRejected,参数是promise的reason2.2.3.2 在promise的状态不是 rejected 之前,不能调用2.2.3.3 onRejected 只能被调用一次2.2.4 onFulfilled 和 onRejected 应该是微任务2.2.5 onFulfilled 和 onRejected 必须作为函数被调用2.2.6 then方法可能被多次调用2.2.6.1 如果promise变成了 fulfilled态,所有的onFulfilled回调都需要按照then的顺序执行2.2.6.2 如果promise变成了 rejected态,所有的onRejected回调都需要按照then的顺序执行2.2.7 then必须返回一个promisepromise2 = promise1.then(onFulfilled, onRejected);2.2.7.1 onFulfilled 或 onRejected 执行的结果为x,调用 resolvePromise2.2.7.2 如果 onFulfilled 或者 onRejected 执行时抛出异常e,promise2需要被reject2.2.7.3 如果 onFulfilled 不是一个函数,promise2 以promise1的值fulfilled2.2.7.4 如果 onRejected 不是一个函数,promise2 以promise1的reason rejected2.3 resolvePromiseresolvePromise(promise2, x, resolve, reject)2.3.1 如果 promise2 和 x 相等,那么 reject promise with a TypeError2.3.2 如果 x 是一个 promsie2.3.2.1 如果x是pending态,那么promise必须要在pending,直到 x 变成 fulfilled or rejected.2.3.2.2 如果 x 被 fulfilled, fulfill promise with the same value.2.3.2.3 如果 x 被 rejected, reject promise with the same reason.2.3.3 如果 x 是一个 object 或者 是一个 function2.3.3.1 let then = x.then.2.3.3.2 如果 x.then 这步出错,那么 reject promise with e as the reason..2.3.3.3 如果 then 是一个函数,then.call(x, resolvePromiseFn, rejectPromise) 2.3.3.3.1 resolvePromiseFn 的 入参是 y, 执行 resolvePromise(promise2, y, resolve, reject); 2.3.3.3.2 rejectPromise 的 入参是 r, reject promise with r. 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。 2.3.3.3.4 如果调用then抛出异常e 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略 2.3.3.3.4.3 否则,reject promise with e as the reason2.3.3.4 如果 then 不是一个function. fulfill promise with x.2.3.4 如果 x 不是一个 object 或者 function,fulfill promise with x. ...

March 8, 2019 · 4 min · jiezi

自己动手实现一个Promise

Promise基本用法Promise 对象是一个代理对象,被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。 这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象一个 Promise有以下几种状态:pending: 初始状态,既不是成功,也不是失败状态。fulfilled: 意味着操作成功完成。rejected: 意味着操作失败。pending 状态的 Promise 对象可能触发fulfilled 状态并传递一个值给相应的状态处理方法,也可能触发失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回promise 对象, 所以它们可以被链式调用。var promise1 = new Promise(function(resolve, reject) { setTimeout(function() { resolve(‘foo’); }, 300);});promise1.then(function(value) { console.log(value); // expected output: “foo”});console.log(promise1);// expected output: [object Promise]Promise/A+然后我们来了解一下Promise规范Promises/A+规范(英文版)Promises/A+规范(中文版)Promise的实现Promise是通过new创建的,可以通过构造函数模式或者是ES6的class来实现,这儿选择构造函数的方式来实现Promise,首先先完成一个简易版本的Promise。function Promise(exector) { var _this = this this.status = ‘pending’ this.value = undefined try { exector(resolve, reject) }catch(e) { reject(e) } function resolve(value) { if(_this.status === ‘pending’) { _this.status = ‘fulfilled’ _this.value = value } } function reject(value) { if(_this.status === ‘pending’) { _this.status = ‘rejected’ _this.value = value } } } Promise.prototype.then = function(resolveCallback, rejectCallback) { if(this.status === ‘fulfilled’) { resolve(this.value) } if(this.status === ‘rejected’) { reject(this.value) } } new Promise((resolve, reject)=> { resolve(‘1’) }).then((data)=> { console.log(‘resolve’ + data) }, (data)=> { console.log(‘reject’ + data) }) //resolve1 new Promise((resolve, reject)=> { setTimeout(()=> { resolve(‘1’) }, 1000) }).then((data)=> { console.log(‘resolve’ + data) }, (data)=> { console.log(‘reject’ + data) }) //无法正常输出上面这个promise中resolve和reject在同步的情况下都能正常输出,但是现在却不支持异步,因为在异步的时候调用then的时候状态还是’pending’,所以resolve/reject并不能如期执行。这个时候就需要一个存放后续调用事件的数组,当异步函数执行完毕后再执行数组中的函数。改进后异步方法可以正常运行:function Promise(exector) { var _this = this this.status = ‘pending’ this.value = undefined this.resolveList = [] this.rejectList = [] try { exector(resolve, reject) }catch(e) { reject(e) } function resolve(value) { if(_this.status === ‘pending’) { _this.status = ‘fulfilled’ _this.value = value _this.resolveList.forEach(item=> { item(_this.value) _this.resolveList.shift() }) } } function reject(value) { if(_this.status === ‘pending’) { _this.status = ‘rejected’ _this.value = value _this.rejectList.forEach(item=> { item(_this.value) _this.rejectList.shift() }) } } } Promise.prototype.then = function(resolveCallback, rejectCallback) { if(this.status === ‘fulfilled’) { resolve(this.value) } if(this.status === ‘rejected’) { reject(this.value) } if(this.status === ‘pending’) { this.resolveList.push(resolveCallback) this.rejectList.push(rejectCallback) } } new Promise((resolve, reject)=> { setTimeout(()=> { resolve(‘1’) }, 1000) }).then((data)=> { console.log(‘resolve’ + data) }, (data)=> { console.log(‘reject’ + data) })链式调用我们可以注意到Promise是可以链式调用的,这就需要then的方法返回一个Promise对象。下面是一个链式调用的简单例子:let promise = new Promise((resolve, reject)=> { resolve(666)})promise.then(data=> { console.log(data) return data + 1}).then(data=> { console.log(data)})//666//667在Promise链中返回Promise:let promise1 = new Promise((resolve, reject)=> { resolve(666)})let promise2 = new Promise((resolve, reject)=> { resolve(999)})promise1.then(data=> { console.log(data) return promise2}).then(data=> { console.log(data)})//666//999关于这种写法需要注意的是,第二个完成处理程序被添加到第三个promise而不是return的promise2,上面的例子等价于:let promise1 = new Promise((resolve, reject)=> { resolve(666)})let promise2 = new Promise((resolve, reject)=> { resolve(999)})let promise3 = promise1.then(data=> { console.log(data) return promise2})promise3.then(data=> { console.log(data)})//666//999当异步的时候调用then函数的时候状态为pending,这个时候同样需要返回一个promise方便后续的链式调用。所以修改为链式调用后的代码为:function Promise(exector) { var _this = this this.status = ‘pending’ this.value = undefined this.resolveList = [] this.rejectList = [] try { exector(resolve, reject) }catch(e) { reject(e) } function resolve(value) { if(_this.status === ‘pending’) { _this.status = ‘fulfilled’ _this.value = value _this.resolveList.forEach(item=> { item(_this.value) _this.resolveList.shift() }) } } function reject(value) { if(_this.status === ‘pending’) { _this.status = ‘rejected’ _this.value = value _this.rejectList.forEach(item=> { item(_this.value) _this.rejectList.shift() }) } }}Promise.prototype.then = function(resolveCallback, rejectCallback) { var _this = this if(this.status === ‘fulfilled’) { return new Promise((resolve, reject)=> { var result = resolveCallback(_this.value) if(result instanceof Promise) { result.then(resolve, reject) }else { resolve(result) } }) } if(this.status === ‘rejected’) { return new Promise((resolve, reject)=> { var result = rejectCallback(_this.value) if(result instanceof Promise) { result.then(resolve, reject) }else { reject(result) } }) } if(this.status === ‘pending’) { return new Promise((resolve, reject)=> { _this.resolveList.push(function() { var result = resolveCallback(_this.value) if(result instanceof Promise) { result.then(resolve, reject) }else { resolve(result) } }) _this.rejectList.push(function() { var result = rejectCallback(_this.value) if(result instanceof Promise) { result.then(resolve, reject) }else { reject(result) } }) }) }}new Promise((resolve, reject)=> { resolve(666)}).then((data)=> { console.log(‘resolve1:’ + data) return 999}).then((data)=> { console.log(‘resolve2:’ + data)})//resolve1: 666//resolve2: 999new Promise((resolve, reject)=> { resolve(666)}).then((data)=> { console.log(‘resolve1:’ + data) return new Promise((resolve, reject)=> { resolve(999) })}).then((data)=> { console.log(‘resolve2:’ + data)})//resolve1: 666//resolve2: 999基本的功能已经实现,下面开始实现Promise的all,race,resolve,reject方法,链式调用部分思路借鉴Promise/A+规范的实现Promise.all()该方法只接受一个有多个受监听的Promise的可迭代对象(比如数组),只有当可迭代对中所有Promise都被解决后才会返回resolve,如果参数中 promise 有一个失败,此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。Promise.all = function(iterable) { return new Promise((resolve, reject) => { let result = [] for(const item of iterable) { item.then(data => { result.push(data) }, reason=> { result = reason return }) } resolve(result) })}//下面是测试用例let p1 = new Promise((resolve, reject) => { resolve(666)})let p2 = new Promise((resolve, reject) => { resolve(888)})let p3 = new Promise((resolve, reject) => { resolve(999)})let p6 = new Promise((resolve, reject) => { reject(222)})let p4 = Promise.all([p1, p2, p3])p4.then(data => { console.log(data)})//[666, 888, 999]let p7 = Promise.all([p1, p3, p6])p7.then(data => { console.log(data)})//222Promise.race()Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。Promise.race = function(iterable) { return new Promise((resolve, reject) => { for(const item of iterable) { item.then(data => { resolve(data) }, reason=> { reject(reson) }) } })}//测试用例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 promise2 is faster});//twoPromise.resolve()Promise.resolve = function(data) { return new Promise((resolve, reject) => { resolve(data) })}//测试用例var p1 = Promise.resolve(123);p1.then(function(value) { console.log(value);});//123Promise.reject()Promise.reject(reason)方法返回一个带有拒绝原因reason参数的Promise对象。Promise.resolve = function(data) { return new Promise((resolve, reject) => { reject(data) })} ...

March 7, 2019 · 4 min · jiezi

顺序加载图片方法

普通图片加载//普通图片加载var imglist = [‘https://avatars3.githubusercontent.com/u/34082804?s=460&v=4’,’./img/2.jpg’,‘https://avatars3.githubusercontent.com/u/34082804?s=460&v=4’,‘https://github.githubassets.com/images/search-key-slash.svg’,’./img/2.jpg’,‘https://avatars3.githubusercontent.com/u/34082804?s=460&v=4’];loadImg(imglist);function loadImg(imglist){ var imgarr = []; var curimg = 0; var body = document.getElementsByTagName(‘body’); for(var i =0;i<imglist.length;i++){ var img = new Image(); img.src = imglist[i]; img.style.width = ‘200px’; img.id = i; img.onload = function(){ console.log(‘show ‘+this.id) } imgarr.push(img); body[0].appendChild(img); }}顺序加载图片方法1//顺序加载图片方法1、function loadImg(imglist){ var imgarr = []; var curimg = 0; var body = document.getElementsByTagName(‘body’); for(var i =0;i<imglist.length;i++){ var img = new Image(); img.src = imglist[i]; img.style.width = ‘200px’; img.onload = function(){ curimg +=1; if(curimg < imgarr.length){ body[0].appendChild(imgarr[curimg]); console.log(‘show ‘+curimg) } } imgarr.push(img); } body[0].appendChild(imgarr[0]);}promise方法//promise方法var imglist = [‘https://avatars3.githubusercontent.com/u/34082804?s=460&v=4’,’./img/2.jpg’,‘https://avatars3.githubusercontent.com/u/34082804?s=460&v=4’,‘https://github.githubassets.com/images/search-key-slash.svg’,’./img/2.jpg’,‘https://avatars3.githubusercontent.com/u/34082804?s=460&v=4’];var body = document.getElementsByTagName(‘body’);function loadimg(i){ var img = new Image(); img.src = imglist[i]; img.style.width = ‘200px’; img.id = i; body[0].appendChild(img); return new Promise(function(resolve,reject){ img.onload = function(){ console.log(‘show ‘+this.id) resolve(i+1) }; })}var a = Promise.resolve(0);var i=0;while(i<imglist.length-1){ a = a.then(function(i){ console.log(’load ‘+i) return loadimg(i) }); i++;}async 方法//async 方法async function loadImages(imglist){ for(var i =0; i<imglist.length; i++){ console.log(’load ‘+i) await loadImg(i); //执行完成之后才走下一步; console.log(‘finish ‘+i); }}async function loadImg(i){ var img = new Image(); img.src = imglist[i]; img.style.width = ‘200px’; img.id = i; body[0].appendChild(img); return new Promise(function(resolve,reject){ img.onload = function(){ console.log(‘show ‘+this.id) resolve(i+1); } })} ...

March 1, 2019 · 1 min · jiezi

你与弄懂promise之间可能只差这篇文章(二)

点我看看~话不多说,上源码:// 核心方法!核心方法!核心方法!也是最难懂的!—-STARTconst resolvePromise = (promise2, x, resolve, reject) => { if (promise2 === x) { return reject(new TypeError(“A promise cannot be resolved with itself”)) } if (x && (typeof x === “object” || typeof x === “function”)) { try { let then = x.then; if (typeof then === “function”) { then.call(x, y => { resolve(y); resolvePromise(promise2, y, resolve, reject); }, r => { reject(r); }) } else { resolve(x); } } catch (e) { reject(e) } } else { resolve(x) }};// 核心方法!核心方法!核心方法!也是最难懂的!—-END// Commitment就是class Commitment { constructor (executor) { this.status = “PENDING” this.value = void(0); this.reason = void(0); this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; const resolve = (value) => { if (this.status === “PENDING”) { this.status = “RESOLVED”; this.value = value; this.onResolvedCallbacks.forEach(cb => cb()) } } const reject = (reason) => { if (this.status === “PENDING”) { this.status = “REJECTED”; this.reason = reason; this.onRejectedCallbacks.forEach(cb => cb()) } } try { executor(resolve, reject) } catch (e) { reject(e) } } then (onResolvedFn, onRejectedFn) { let promise2, x; promise2 = new Promise((resolve, reject) => { if (this.status === “RESOLVED”) { setTimeout(() => { try { x = onResolvedFn(this.value); resolvePromise(promise2, x, resolve, reject); } catch(e) { reject(e) } }, 0) } if (this.status === “REJECTED”) { setTimeout(() => { try { x = onRejectedFn(this.reason); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) } if (this.status === “PENDING”) { this.onResolvedCallbacks.push(() => { setTimeout(() => { try { x = onResolvedFn(this.value); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { x = onRejectedFn(this.reason); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) }); } }); return promise2 } catch (onRejectedFn) { return this.then(null, onRejectedFn) } // 静态方法 static resolve (value) { return new Commitment((resolve, reject) => { resolve(value) }) } static reject (err) { return new Commitment((resolve, reject) => { reject(err) }) } static all () { let result = []; return new Commitment((resolve, reject) => { if (arguments.length === 0) { return reject(new TypeError(“undefined is not iterable (cannot read property Symbol(Symbol.iterator))”)) } else { let args = […arguments][0], result = []; let count = args.length, num = 0; for (let i = 0; i < count; i++) { let item = args[i]; try { let then = item.then; if (typeof then === “function”) { then.call(item, y => { num += 1; result[i] = y; if (num === count) { resolve(result) } }, r => { reject(r) }) } else { result[i] = item; if (num === count) { resolve(result) } } } catch (e) { reject(e) } } } }) } static race () { // 未完待续 }} ...

February 28, 2019 · 3 min · jiezi

promise

前言今天来分享下promise的用法,es6伟大发明之一,当初我学习的时候也是蛮头大的,不知道为啥,整个脑子就是,我在哪,我要干啥的懵圈,后面认真学习之后,觉得真是十分好用,下面就来一起学习下吧。为什么会有promise首先为什么会有promise的存在,其实很多人都知道的,其中最大的问题就是在处理多个有依赖关系的异步操作时,会出现回调地狱( callback hell ),如下:$.ajax({ url: ’….’, success: function (data) { $.ajax({ url: ’….’, success: function (data) { } }); } });promise提供了一个优雅的方式,来解决这个问题,同时提供了很多的错误捕获机制。如何使用promise我们先不讲promise的理论语法,这样会一开始就降低学习的欲望,直接来看使用案例,然后去理解。首先看基本使用new Promise(function (resolve, reject) { // 假设此处是异步请求某个数据 $.ajax({ url: ’……’, success: function (res) { if (res.code === 200) { resolve(res.data); } else { reject(‘获取data失败’); } } })}).then(function A(data) { // 成功,下一步 console.log( data); }, function B(error) { // 失败,做相应处理 console.log(error) });console:sucesserror解析:梳理流程:首先我们在promise函数里,执行我们的异步操作得到data如果成功的话,通过resolve函数数据传递出来,如果失败。通过reject把错误信息传递出来然后在.then里可以接受传递出来的数据,.then()里面接受两个函数,第一个函数接收resolve传递出来的值,也就是正确情况下的处理,第二个函数接收reject传递的信息,也就是错误的情况下的处理。Promise是一个对象,它的内部其实有三种状态。初始状态( pending )。已完成( fulfilled ): Promise 的异步操作已结束成功。已拒绝( rejected ): Promise 的异步操作未成功结束。resolve 方法可以使 Promise 对象的状态改变成成功,同时传递一个参数用于后续成功后的操作。reject 方法则是将 Promise 对象的状态改变为失败,同时将错误的信息传递到后续错误处理的操作。then(onFulfilled, onRejected)—(onFulfilled, onRejected)链式then当然,我们既然解决回调地狱,一个异步,看不出来啥优势,现在看多个异步请求, 为了代码简约,我们用setTimeout来代替ajax请求 作为异步操作,如下:new Promise((resolve, reject) => { setTimeout( () => { if (…){ resolve([1, 2, 3]) } else { reject(’error’); } }, 2000);}) .then( data => { console.log(value); // 打印出[1, 2, 3] return new Promise( (resolve, reject)=> { // 新的异步请求,需要promise对象 let data2 = 1 + data; setTimeout( () => { if (…) { resolve(data2); } else { reject(’error2’) } }, 2000); }); }, error => { cosnole.log(error) }).then( data2 => { console.log(data2 ); }, error => { cosnole.log(error) });解析:-这个例子中,第一个异步操作得到数据[1, 2, 3],传递到第一个then中,我们在第一个then中运用拿到的数据,进行第二次异步操作,并把结果传递出去。在第二个then中拿到数据,并且捕获error。可以看到本来嵌套的两个异步操作,现在清晰多了,而且链式接无数个then在这里有两个地方需要注意then里面的可捕获错误的函数,可以捕获到上面的所有then的错误,所以只在最后一个then里,写错误捕获函数就可以。每次异步操作时候需要返回一个新的promise,因为只有用promise对象才会等异步操作执行完,才去执行下面的then,才能拿到异步执行后的数据,所以第二个then里的异步请求,也需要声明Promise对象。如果then里面返回常量,可以直接返回。如下:new Promise((resolve, reject) => { setTimeout( () => { if (…){ resolve([1, 2, 3]) } else { reject(’error’); } }, 2000);}) .then( value => { return ‘222’; // 如果是直接返回常量,可直接return }) .then( value2 => { console.log(value2 ); // 打印出222 })下面忽略error情况,看两个例子,大家可以自己思考下打印结果new Promise(resolve => { setTimeout( () => { resolve(‘value1’); }, 2000);}) .then( value1 => { console.log(value1); (function () { return new Promise(resolve => { setTimeout(() => { console.log(‘Mr.Laurence’); resolve(‘Merry Xmas’); }, 2000); }); }()); return false; }) .then( value => { console.log(value + ’ world’); });value1false worldMr.Laurencenew Promise( resolve => { console.log(‘Step 1’); setTimeout(() => { resolve(100); }, 1000);}).then( value => { return new Promise(resolve => { console.log(‘Step 1-1’); setTimeout(() => { resolve(110); }, 1000); }) .then( value => { console.log(‘Step 1-2’); return value; }) .then( value => { console.log(‘Step 1-3’); return value; });}).then(value => { console.log(value); console.log(‘Step 2’);});console:Step 1Step 1-1Step 1-2Step 1-3110Step 2catchcatch 方法是 then(onFulfilled, onRejected) 方法当中 onRejected 函数的一个简单的写法,也就是说可以写成 then(fn).catch(fn),相当于 then(fn).then(null, fn)。使用 catch 的写法比一般的写法更加清晰明确。我们在捕获错误的时候,直接在最后写catch函数即可。 let promise = new Promise(function(resolve, reject) { throw new Error(“Explosion!”);});promise.catch(function(error) { console.log(error.message); // “Explosion!”});上面代码等于与下面的代码 let promise = new Promise(function(resolve, reject) { throw new Error(“Explosion!”);});promise.catch(function(error) { console.log(error.message); // “Explosion!”});异步代码错误抛出要用rejectnew Promise( resolve => { setTimeout( () => { throw new Error(‘bye’); }, 2000);}).then( value => { }).catch( error => { console.log( ‘catch’, error); });控制台会直接报错 Uncaught Error: bye解析:因为异步情况下,catch已经执行完了,错误才抛出,所以无法捕获,所以要用reject,如下:new Promise( (resolve, reject) => { setTimeout( () => { reject(‘bye’); }, 2000);}).then( value => { console.log( value + ’ world’); }).catch( error => { console.log( ‘catch’, error); });catch bye利用reject可以抓捕到promise里throw的错catch 可以捕获then里丢出来的错,且按顺序只抓捕第一个没有被捕获的错误new Promise( resolve => { setTimeout( () => { resolve(); }, 2000);}).then( value => { throw new Error(‘bye’); }).then( value => { throw new Error(‘bye2’); }).catch( error => { console.log( ‘catch’, error); });console: Error: byenew Promise( resolve => { setTimeout( () => { resolve(); }, 2000);}).then( value => { throw new Error(‘bye’); }).catch( error => { console.log( ‘catch’, error); }).then( value => { throw new Error(‘bye2’); }).catch( error => { console.log( ‘catch’, error); });console: Error: byeconsole: Error: bye2catch 抓捕到的是第一个没有被捕获的错误错误被捕获后,下面代码可以继续执行new Promise(resolve => { setTimeout(() => { resolve(); }, 1000);}) .then( () => { throw new Error(’test1 error’); }) .catch( err => { console.log(‘I catch:’, err); // 此处捕获了 ’test1 error’,当错误被捕获后,下面代码可以继续执行 }) .then( () => { console.log(’ here’); }) .then( () => { console.log(‘and here’); throw new Error(’test2 error’); }) .catch( err => { console.log(‘No, I catch:’, err); // 此处捕获了 ’test2 error’ });I catch: Error: test2 errorhereand here I catch: Error: test2 error错误在捕获之前的代码不会执行new Promise(resolve => { setTimeout(() => { resolve(); }, 1000);}) .then( () => { throw new Error(’test1 error’); }) .catch( err => { console.log(‘I catch:’, err); // 此处捕获了 ’test1 error’,不影响下面的代码执行 throw new Error(‘another error’); // 在catch里面丢出错误,会直接跳到下一个能被捕获的地方。 }) .then( () => { console.log(‘and here’); throw new Error(’test2 error’); }) .catch( err => { console.log(‘No, I catch:’, err); // 此处捕获了 ’test2 error’ });I catch: Error: test2 errorI catch: Error: another errornew Promise(resolve => { setTimeout(() => { resolve(); }, 1000);}) .then( () => { console.log(‘start’); throw new Error(’test1 error’); }) .then( () => { console.log(‘arrive here’); }) .then( () => { console.log(’… and here’); throw new Error(’test2 error’); }) .catch( err => { console.log(‘No, I catch:’, err); // 捕获到了第一个 });No, I catch: Error: test1 error at Promise.then (<anonymous>:8:1Promise.allPromise.all([1, 2, 3]) .then( all => { console.log(‘1:’, all); })[1, 2, 3]Promise.all([function () {console.log(‘ooxx’);}, ‘xxoo’, false]) .then( all => { console.log( all); }); [ƒ, “xxoo”, false]let p1 = new Promise( resolve => { setTimeout(() => { resolve(‘I'm P1’); }, 1500);});let p2 = new Promise( (resolve, reject) => { setTimeout(() => { resolve(‘I'm P2’); }, 1000); });let p3 = new Promise( (resolve, reject) => { setTimeout(() => { resolve(‘I'm P3’); }, 3000); }); Promise.all([p1, p2, p3]).then( all => { console.log(‘all’, all);}).catch( err => { console.log(‘Catch:’, err);});all (3) [“I’m P1”, “I’m P2”, “I’m P3”]案例:删除所有数据后,做一些事情、、、、db.allDocs({include_docs: true}).then(function (result) { return Promise.all(result.rows.map(function (row) { return db.remove(row.doc); }));}).then(function (arrayOfResults) { // All docs have really been removed() now!});Promise.resolvePromise.resolve() .then( () => { console.log(‘Step 1’); })其他Promise.resolve(‘foo’).then(Promise.resolve(‘bar’)).then(function (result) { console.log(result);});VM95:2 foo如果你向 then() 传递的并非是一个函数(比如 promise)它实际上会将其解释为 then(null),这就会导致前一个 promise 的结果会穿透下面How do I gain access to resultA here?function getExample() { return promiseA(…).then(function(resultA) { // Some processing return promiseB(…); }).then(function(resultB) { // More processing return // How do I gain access to resultA here? });}解决 Break the chainfunction getExample() { var a = promiseA(…); var b = a.then(function(resultA) { // some processing return promiseB(…); }); return Promise.all([a, b]).then(function([resultA, resultB]) { // more processing return // something using both resultA and resultB });}

February 27, 2019 · 5 min · jiezi

Ajax Fetch 和 Axios(持续更新中...)

Ajax Fetch 和 Axios(持续更新中…)知识点梳理AJAX 不是 JavaScript 的规范,它只是一个哥们“发明”的缩写:Asynchronous JavaScript and XML,意思就是用 JavaScript 执行异步网络请求。JavaScript 代码都是单线程执行的,由于这个“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现,回调函数不好看,不利于代码复用,而链式写法好处 逻辑统一、利于复用,所以出现 Primose Promise 有各种开源实现,在 ES6 中被统一规范,由浏览器直接支持。async await 是 Promise 语法糖,使异步的逻辑书写标准的同步函数Generator原生 XHRAjaxAJAX 不是 JavaScript 的规范,它只是一个哥们“发明”的缩写:Asynchronous JavaScript and XML,意思就是用 JavaScript 执行异步网络请求。属性描述onreadystatechange每当 readyState 属性改变时,就会调用该函数。readyState存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。0: 请求未初始化 1: 服务器连接已建立,open()方法已调用,但是 send()方法未调用 2: 请求已连接 send()方法已调用,HTTP 请求已发送到 Web 服务器。未接收到相应3: 请求处理中 4: 请求已完成,且响应已就绪status200: “OK"404: 未找到页面;(function() { var xmlhttp if (window.XMLHttpRequest) { // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码 xmlhttp = new XMLHttpRequest() } else { // IE6, IE5 浏览器执行代码 xmlhttp = new ActiveXObject(‘Microsoft.XMLHTTP’) } xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { console.log(xmlhttp.responseText) } } xmlhttp.open(‘GET’, ‘http://www.runoob.com/try/ajax/ajax_info.txt', true) xmlhttp.send()})()Ajax 安全限制浏览器的同源策略导致的。默认情况下,JavaScript 在发送 AJAX 请求时,URL 的域名必须和当前页面完全一致跨域请求通过 Flash 插件发送 HTTP 请求,这种方式可以绕过浏览器的安全限制,但必须安装 Flash,并且跟 Flash 交互。不过 Flash 用起来麻烦,而且现在用得也越来越少了。通过在同源域名下架设一个代理服务器来转发,JavaScript 负责把请求发送到代理服务器JSONP 它有个限制,只能用 GET 请求,并且要求返回 JavaScript。这种方式跨域实际上是利用了浏览器允许跨域引用 JavaScript 资源CORS CORS 全称 Cross-Origin Resource Sharing,是 HTML5 规范定义的如何跨域访问资源。面这种跨域请求,称之为“简单请求”。简单请求包括 GET、HEAD 和 POST(POST 的 Content-Type 类型仅限 application/x-www-form-urlencoded、multipart/form-data 和 text/plain),并且不能出现任何自定义头(例如,X-Custom: 12345),通常能满足 90%的需求Promise在 JavaScript 的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现function callback() { console.log(‘Done’)}console.log(‘before setTimeout()’)setTimeout(callback, 1000) // 1秒钟后调用callback函数console.log(‘after setTimeout()’)链式写法的好处在于,先统一执行 AJAX 逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用 success 函数或 fail 函数。古人云:“君子一诺千金”,这种“承诺将来会执行”的对象在 JavaScript 中称为 Promise 对象。使用 Promise 封装 ajax 简化异步处理// ajax函数将返回Promise对象:function ajax(method, url, data) { var request = new XMLHttpRequest() return new Promise(function(resolve, reject) { request.onreadystatechange = function() { if (request.readyState === 4) { if (request.status === 200) { resolve(request.responseText) } else { reject(request.status) } } } request.open(method, url) request.send(data) })}var p = ajax(‘GET’, ‘/api/categories’)p.then(function(text) { // 如果AJAX成功,获得响应内容}).catch(function(status) { // 如果AJAX失败,获得响应代码})Promise 使用方法;(function() { console.time(‘doIt’) const time1 = 300 step1(time1) .then(time2 => step2(time2)) .then(time3 => step3(time3)) .then(result => { console.log(result is ${result}) console.timeEnd(‘doIt’) })})()function takeLongTime(n) { return new Promise(resolve => { setTimeout(() => resolve(n + 200), n) })}function step1(n) { console.log(step1 with ${n}) return takeLongTime(n)}function step2(n) { console.log(step2 with ${n}) return takeLongTime(n)}function step3(n) { console.log(step3 with ${n}) return takeLongTime(n)}Promise.all()两个任务是可以并行执行的,用 Promise.all()实现var p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, ‘P1’)})var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 6000, ‘P2’)})// 同时执行p1和p2,并在它们都完成后执行then:Promise.all([p1, p2]).then(function(results) { console.log(results) // 获得一个Array: [‘P1’, ‘P2’]})Promise.race()有些时候,多个异步任务是为了容错,只需要获得先返回的结果即可var p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 3000, ‘P1’)})var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 6000, ‘P2’)})// 同时执行p1和p2,其中一个完成后执行then:Promise.race([p1, p2]).then(function(results) { console.log(results) // // ‘P1’})async awaitawaitawait 操作符用于等待一个 Promise 对象。它只能在异步函数 async function 中使用await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的 resolve 函数参数作为 await 表达式的值,继续执行 async function。若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。另外,如果 await 操作符后的表达式的值不是一个 Promise,则返回该值本身。// 如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x) }, 2000) })}async function f1() { var x = await resolveAfter2Seconds(10) console.log(x) // 10}f1()// 如果 Promise 处理异常,则异常值被抛出。async function f3() { try { var z = await Promise.reject(30) } catch (e) { console.log(e) // 30 }}f3()asyncasync function 声明用于定义一个返回 AsyncFunction 对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果。一个返回的 Promise 对象会以 async function 的返回值进行解析(resolved),或者以该函数抛出的异常进行回绝(rejected)。async 函数中可能会有 await 表达式,这会使 async 函数暂停执行,等待 Promise 的结果出来,然后恢复 async 函数的执行并返回解析值(resolved)。function resolveAfter2Seconds() { return new Promise(resolve => { setTimeout(() => { resolve(‘resolved’) }, 2000) })}async function asyncCall() { console.log(‘calling’) var result = await resolveAfter2Seconds() console.log(result) // expected output: ‘resolved’}asyncCall()GeneratorFetchFetch 是一个现代的概念, 等同于 XMLHttpRequest。它提供了许多与 XMLHttpRequest 相同的功能,但被设计成更具可扩展性和高效性。Fetch 是挂在在 window 下的Fetch 的核心在于对 HTTP 接口的抽象,包括 Request,Response,Headers,Body,以及用于初始化异步请求的 global fetch。Fetch 还利用到了请求的异步特性——它是基于 Promise 的。fetch(‘http://example.com/movies.json') .then(function(response) { return response.json() }) .then(function(myJson) { console.log(myJson) })postData(‘http://example.com/answer', { answer: 42 }) .then(data => console.log(data)) // JSON from response.json() call .catch(error => console.error(error))function postData(url, data) { // Default options are marked with * return fetch(url, { body: JSON.stringify(data), // must match ‘Content-Type’ header cache: ’no-cache’, // *default, no-cache, reload, force-cache, only-if-cached credentials: ‘same-origin’, // include, same-origin, *omit headers: { ‘user-agent’: ‘Mozilla/4.0 MDN Example’, ‘content-type’: ‘application/json’ }, method: ‘POST’, // *GET, POST, PUT, DELETE, etc. mode: ‘cors’, // no-cors, cors, *same-origin redirect: ‘follow’, // manual, *follow, error referrer: ’no-referrer’ // *client, no-referrer }).then(response => response.json()) // parses response to JSON}参考AJAXPromiseasync await 语法描述Fetch 语法描述 ...

February 21, 2019 · 3 min · jiezi

手写一款符合Promise/A+规范的Promise

手写一款符合Promise/A+规范的Promise长篇预警!有点长,可以选择性观看。如果对Promise源码不是很清楚,还是推荐从头看,相信你认真从头看到尾,并且去实际操作了,肯定会有收获的。主要是代码部分有点多,不过好多都是重复的,不必担心Promise的一些用法在此不多赘述,本篇主要带领你手写一个Promise源码,学完你就会发现:Promise没有你想象中的那么难本篇大概分为以下步骤实现简单的同步Promise增加异步功能增加链式调用then增加catch finally方法增加all race 等方法实现一个promise的延迟对象defer最终测试实现简单的同步Promise先大概说一下基本概念:Promise内部维护着三种状态,即pending,resolved和rejected。初始状态是pending,状态可以有pending—>relolved,或者pending—>rejected.不能从resolve转换为rejected 或者从rejected转换成resolved.即 只要Promise由pending状态转换为其他状态后,状态就不可变更。ok.知道了这些后,我们开始手撸代码:注意观看序号 1 2 3 4 5 …function Promise(executor){ let that = this; /** 2 定义初始的一些变量 / that.status = ‘pending’; that.value = null; that.reason = null; /* 3 定义初始的成功和失败函数 / function resolve(value){ /* 4 判断状态是不是初始状态pending * 是就转换状态 否则不转换 * 确保状态的变化后的不可变性 / if(that.status === ‘pending’){ that.status = ‘resolved’; that.value = value; } } function reject(reason){ /* 5 同上 / if(that.status === ‘pending’){ that.status = ‘rejected’; that.reason = reason; } } /* * 1 Promise中首先传了一个executor,它是一个函数 * executor函数中又传了两个函数,分别是resolve和reject * 很显然 resolve是成功回调,reject是失败的回调 / executor(resolve,reject);}/* 6 在Promise原型上面定义then方法 * then方法上面有两个回调 一个是成功后的方法 另一个是失败后的方法 * 根据成功或失败的状态去执行相关成功onFilfulled()或者失败onRejected()的回调方法 /Promise.prototype.then = function(onFilfulled,onRejected){ let that = this; if(that.status === ‘resolved’){ /* 7 如果状态已经变更为resolved * 说明resolve方法已经被调用 * 那么此时就执行成功的回调函数onFilfulled * 并传入参数 that.value * / onFilfulled(that.value); } if(that.status === ‘rejected’){ /* 8 同上 * 传入参数 that.reason / onRejected(that.reason); }}module.exports = Promise;通过require()引入手撸的Promiselet Promise = require(’./myPromise’);let p1 = ()=>{ return new Promise((resolve,reject)=>{ resolve(‘success.1’); });}p1().then((data)=>{ console.log(data); // 打印 success.1},(err)=>{ console.log(err);});ok.经调用发现 此代码可以实现部分Promise的功能,但仅仅是同步下才有效果。那异步呢? 别急这就来~:增加异步功能注意观看序号 1 2 3 4 5 …function Promise(executor){ let that = this; that.status = ‘pending’; that.value = null; that.reason = null; /* 1 因为异步不是立即执行 状态不会变更 成功或失败的回调函数也不会执行 * 所以先定义好存放成功或失败回调函数的数组 * 以便将成功或失败的回调函数先保存起来 * / that.onFilFulledCallbacks = []; that.onRejectedCallbacks = []; function resolve(value){ if(that.status === ‘pending’){ that.status = ‘resolved’; that.value = value; /* 3 发布 * 等待状态发生变更 * 状态变更后 立即执行之前存放在相应数组中所有的成功或失败的回调函数 * 即 发布 / that.onFilFulledCallbacks.forEach((fn)=>{ fn(); }); } } function reject(reason){ if(that.status === ‘pending’){ that.status = ‘rejected’; that.reason = reason; /* 4 同上 / that.onRejectedCallbacks.forEach((fn)=>{ fn(); }); } } executor(resolve,reject);}Promise.prototype.then = function(onFilfulled,onRejected){ let that = this; if(that.status === ‘resolved’){ onFilfulled(that.value); } if(that.status === ‘rejected’){ onRejected(that.reason); } /* 2 订阅 * 因为是异步 状态当时并没有立即变更 所以状态还是pending * 此时需要把成功或者失败的回调函数存放到对应的数组中 * 等待状态变更时 再从数组中拿出来去执行 * 即 订阅 * 存放数组时 为了执行时方便 需要把回调函数的外层包裹一层空函数 / if(that.status === ‘pending’){ that.onFilFulledCallbacks.push(function(){ onFilfulled(that.value); }); } if(that.status === ‘pending’){ that.onRejectedCallbacks.push(function(){ onRejected(that.reason); }); }}module.exports = Promise;代码测试:let Promise = require(’./myPromise’);let p1 = ()=>{ return new Promise((resolve,reject)=>{ setTimeout(function(){ resolve(‘success.1’); // reject(‘fail.’); },1500); });}p1().then((data)=>{ console.log(data); // success.1},(err)=>{ console.log(err);});可以看到 1.5s后 执行了resolve() 并打印了success.1,至此,我们实现了异步的Promise.其实这里的实现异步的思想就是发布订阅.en~ok.高能预警????.接下来就稍稍复杂了 因为我们要实现链式调用then。 要实现这个功能那我们就要重写then方法,并在then方法中重新返回一个Promise,只有这样,才可以实现多次调用then.而且要新增一个解析返回值是否为promise的函数.稍微捋下逻辑:如果一个then方法返回一个普通值的话,这个值会传递给下一个then中作为resolve成功的结果如果一个then方法返回一个promise的话,会根据返回的promise是成功还是失败,决定下一个then是成功还是失败???? 上代码:增加链式调用then注意观看序号 1 2 3 4 5 …function Promise(executor){ let that = this; that.status = ‘pending’; that.value = null; that.reason = null; that.onFilFulledCallbacks = []; that.onRejectedCallbacks = []; function resolve(value){ if(that.status === ‘pending’){ that.status = ‘resolved’; that.value = value; that.onFilFulledCallbacks.forEach((fn)=>{ fn(); }); } } function reject(reason){ if(that.status === ‘pending’){ that.status = ‘rejected’; that.reason = reason; that.onRejectedCallbacks.forEach((fn)=>{ fn(); }); } } executor(resolve,reject);}Promise.prototype.then = function(onFilfulled,onRejected){ let that = this; / 1 让promise2等于一个新的Promise 并将promise2返回 / let promise2 = new Promise((resolve,reject)=>{ if(that.status === ‘resolved’){ /* 2 因为返回了promise2 * 并且第3步resolvePromiseRelation函数中传递了promise2 * 而目前promise2并没有拿到 * 所以加一个定时器 异步执行 等到promise2拿到后 * 再去执行 resolvePromiseRelation()方法 并将promise2传递进去*/ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); /** 3 判断新返回值是什么类型的函数 * 并将当前的promise:promise2 新的返回值:promise3 * 和 成功时回调:esolve 失败时回调:reject 作为参数传进去 / resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === ‘rejected’){ /* 同2 / setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); /* 同3*/ resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === ‘pending’){ that.onFilFulledCallbacks.push(function(){ /** 同2 / setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); /* 同3*/ resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); } if(that.status === ‘pending’){ that.onRejectedCallbacks.push(function(){ /** 同2 / setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); /* 同3*/ resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); } }); /** 同1 / return promise2;}function resolvePromiseRelation(promise2,promise3,resolve,reject){ /* 4 防止自己等待自己 一直循环等待 / if(promise2 === promise3){ return reject(new TypeError(‘循环引用了!’)); } /* 8 一个标示 表示当前没有被调用过 * 确保resolve或者reject后的状态不会再次发生变更 / let called; /* 5 保证promise3是一个引用类型 * 判断新返回值promise3的类型 * 如果是普通值常量 就直接resolve导出 / if(promise3!==null&&(typeof promise3 === ‘object’||typeof promise3 === ‘function’)){ try{ /* 6 确保promise3是一个Promise * 判断promise3的then方法 * 如果存在 并且是一个function类型 * 就表示promise3是一个Promise / if(typeof promise3.then === ‘function’){ /* 9 执行promise3的then方法 * 因为promise3也是一个Promise * 需要再次解析promise3的then方法 * 直到解析到最后的返回值不是一个Promise类型为止 / promise3.then(promise3, (promise4)=>{ /* 同8 / if(called) return; called = true; /* 10 递归解析新的返回值的类型 * 解析到返回值不是一个Promise类型为止 / resolvePromiseRelation(promise3,promise4,resolve,reject); },(r)=>{ /* 同8 / if(called) return; called = true; reject(r); }); }else{ /* 7 此时promise3是一个普通对象 直接resolve() / resolve(promise3); } }catch(e){ /* 同8 / if(called) return; called = true; reject(e); }; }else{ /* 同5 普通值直接resolve()/ resolve(promise3); }}module.exports = Promise;ok. 至此 我们已经实现了Promsie的异步和链式调用. Promise中比较复杂的部分我们已经搞定了 接下来就是添加一些方法,其实这部分反而没那么复杂了.catch : catch方法本质上就是一个then方法的变形,只有失败时的回调 没有成功时的回调finally : finally方法的作用是不管 Promise 对象最后状态如何,都会执行操作.其实说白了就是在then方法的成功和失败的回调函数中都执行该方法就行了.ok.老规矩 上代码~增加catch finally方法function Promise(executor){ let that = this; that.status = ‘pending’; that.value = null; that.reason = null; that.onFilFulledCallbacks = []; that.onRejectedCallbacks = []; function resolve(value){ if(that.status === ‘pending’){ that.status = ‘resolved’; that.value = value; that.onFilFulledCallbacks.forEach((fn)=>{ fn(); }); } } function reject(reason){ if(that.status === ‘pending’){ that.status = ‘rejected’; that.reason = reason; that.onRejectedCallbacks.forEach((fn)=>{ fn(); }); } } executor(resolve,reject);}Promise.prototype.then = function(onFilfulled,onRejected){ /** 2 此处有个坑 如果只写1 不写2的话 * 会报一个TypeError :onRejected is not a function * 在此处给它一个默认的成功和失败的回调函数就好 / onFilfulled = typeof onFilfulled === ‘function’?onFilfulled:value=>value; onRejected = typeof onRejected === ‘function’?onRejected:err=>{throw err}; let that = this; let promise2 = new Promise((resolve,reject)=>{ if(that.status === ‘resolved’){ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === ‘rejected’){ setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === ‘pending’){ that.onFilFulledCallbacks.push(function(){ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); } if(that.status === ‘pending’){ that.onRejectedCallbacks.push(function(){ setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); } }); return promise2;}function resolvePromiseRelation(promise2,promise3,resolve,reject){ if(promise2 === promise3){ return reject(new TypeError(‘循环引用了!’)); } let called; if(promise3!==null&&(typeof promise3 === ‘object’||typeof promise3 === ‘function’)){ try{ if(typeof promise3.then === ‘function’){ promise3.then(promise3, (promise4)=>{ if(called) return; called = true; resolvePromiseRelation(promise3,promise4,resolve,reject); },(r)=>{ if(called) return; called = true; reject(r); }); }else{ resolve(promise3); } }catch(e){ if(called) return; called = true; reject(e); }; }else{ resolve(promise3); }}/* 1 直接返回this的then方法 * 因为catch只捕获错误 所以resolve直接为null * 返回reject就好/Promise.prototype.catch = function(errFn){ return this.then(null,errFn);}/** 3 finally实现起来也很简单 * 分别在resolve和reject中执行fn就好 * 最后再把this返回出去就好*/Promise.prototype.finally = function(fn){ this.then(()=>{ fn(); },()=>{ fn(); }); return this;}module.exports = Promise;增加all race 等方法function Promise(executor){ let that = this; that.status = ‘pending’; that.value = null; that.reason = null; that.onFilFulledCallbacks = []; that.onRejectedCallbacks = []; function resolve(value){ if(that.status === ‘pending’){ that.status = ‘resolved’; that.value = value; that.onFilFulledCallbacks.forEach((fn)=>{ fn(); }); } } function reject(reason){ if(that.status === ‘pending’){ that.status = ‘rejected’; that.reason = reason; that.onRejectedCallbacks.forEach((fn)=>{ fn(); }); } } executor(resolve,reject);}Promise.prototype.then = function(onFilfulled,onRejected){ onFilfulled = typeof onFilfulled === ‘function’?onFilfulled:value=>value; onRejected = typeof onRejected === ‘function’?onRejected:err=>{throw err}; let that = this; let promise2 = new Promise((resolve,reject)=>{ if(that.status === ‘resolved’){ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === ‘rejected’){ setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === ‘pending’){ that.onFilFulledCallbacks.push(function(){ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); } if(that.status === ‘pending’){ that.onRejectedCallbacks.push(function(){ setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); } }); return promise2;}function resolvePromiseRelation(promise2,promise3,resolve,reject){ if(promise2 === promise3){ return reject(new TypeError(‘循环引用了!’)); } let called; if(promise3!==null&&(typeof promise3 === ‘object’||typeof promise3 === ‘function’)){ try{ if(typeof promise3.then === ‘function’){ promise3.then(promise3, (promise4)=>{ if(called) return; called = true; resolvePromiseRelation(promise3,promise4,resolve,reject); },(r)=>{ if(called) return; called = true; reject(r); }); }else{ resolve(promise3); } }catch(e){ if(called) return; called = true; reject(e); }; }else{ resolve(promise3); }}Promise.prototype.catch = function(errFn){ return this.then(null,errFn);}Promise.prototype.finally = function(fn){ this.then(()=>{ fn(); },()=>{ fn(); }); return this;}/** 1 直接在构造函数上增加all方法 * 它返回的也是一个Promise * 等待参数数组中所有的promise都执行完毕后 * 再返回结果 /Promise.all = function(values){ return new Promise((resolve,reject)=>{ /* 2 定义一个存放最终结果的数组result和一个index / let results = []; let index = 0; /* 3 定义一个方法addToArr() * 让index每次执行增加results数组元素的函数的时候都+1 * 当index === values的长度的时候 说明此时所有promsie都执行完毕并放到的数组中 * 然后直接resolve(results)就行了 / function addToArr(key,value){ index++; results[key] = value; /* 6 当满足条件时 说明所有的promise都执行完毕 直接resolve(results) / if(index === values.length){ resolve(results); } } /* 4 循环values中的每一项promsie / for(let i = 0; i < values.length; i++){ let current = values[i]; /* 5 判断每一项promise的返回值是不是一个Promsie * 是的话就执行该Promise的then方法 拿到返回值 并放到数组results中 * 是一个普通值的话就直接将该值放到数组results中 / if(current && current.then && typeof current.then === ‘function’){ current.then((value)=>{ /* 同5 把返回值放到数组results中*/ addToArr(i,value); },reject); }else{ /** 同5 把返回值放到数组results中*/ addToArr(i,current); } } });}/** race方法相比较于all方法简单很多 * 因为race中的promsie成功resolve一个 * 整个race就resolve /Promise.race = function(values){ return new Promise((resolve,reject)=>{ /* 同4 / for(let i = 0; i < values.length; i++){ let current = values[i]; /* 同5 / if(current&&current.then&&typeof current.then === ‘function’){ /* 7 直接执行then就好 / current.then(resolve,reject); }else{ /* 8 普通值直接resolve */ resolve(current); } } });}module.exports = Promise;实现一个promise的延迟对象defer此步是为了测试我们手写的Promsie符不符合Promsie/A+规范,如果没有defer的话,我们在测试过程中就会报一个TypeError: adapter.deferred is not a function.其实写完defer后,我们就可以去进行测试我们手写的Promsie符不符合Promsie/A+规范了。即:本篇手写一款符合Promise/A+规范的Promise的最终本为:function Promise(executor){ let that = this; that.status = ‘pending’; that.value = null; that.reason = null; that.onFilFulledCallbacks = []; that.onRejectedCallbacks = []; function resolve(value){ if(that.status === ‘pending’){ that.status = ‘resolved’; that.value = value; that.onFilFulledCallbacks.forEach((fn)=>{ fn(); }); } } function reject(reason){ if(that.status === ‘pending’){ that.status = ‘rejected’; that.reason = reason; that.onRejectedCallbacks.forEach((fn)=>{ fn(); }); } } executor(resolve,reject);}Promise.prototype.then = function(onFilfulled,onRejected){ onFilfulled = typeof onFilfulled === ‘function’?onFilfulled:value=>value; onRejected = typeof onRejected === ‘function’?onRejected:err=>{throw err}; let that = this; let promise2 = new Promise((resolve,reject)=>{ if(that.status === ‘resolved’){ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === ‘rejected’){ setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === ‘pending’){ that.onFilFulledCallbacks.push(function(){ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); that.onRejectedCallbacks.push(function(){ setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); } }); return promise2;}function resolvePromiseRelation(promise2,promise3,resolve,reject){ if(promise2 == promise3){ return reject(new TypeError(‘循环引用了!’)); } let called; if(promise3!==null&&(typeof promise3 === ‘object’ || typeof promise3 === ‘function’)){ try{ if(typeof promise3.then === ‘function’){ promise3.then.call(promise3, (promise4)=>{ if(called) return; called = true; resolvePromiseRelation(promise3,promise4,resolve,reject); },(r)=>{ if(called) return; called = true; reject(r); }); }else{ resolve(promise3); } }catch(e){ if(called) return; called = true; reject(e); }; }else{ resolve(promise3); }}Promise.prototype.catch = function(errFn){ return this.then(null,errFn);}Promise.prototype.finally = function(fn){ this.then(()=>{ fn(); },()=>{ fn(); }); return this;}Promise.all = function(values){ return new Promise((resolve,reject)=>{ let results = []; let index = 0; function addToArr(key,value){ index++; results[key] = value; if(index === values.length){ resolve(results); } } for(let i = 0; i < values.length; i++){ let current = values[i]; if(current && current.then && typeof current.then === ‘function’){ current.then((value)=>{ addToArr(i,value); },reject); }else{ addToArr(i,current); } } });}Promise.race = function(values){ return new Promise((resolve,reject)=>{ for(let i = 0; i < values.length; i++){ let current = values[i]; if(current&&current.then&&typeof current.then === ‘function’){ current.then(resolve,reject); }else{ resolve(current); } } });}// 实现一个promise的延迟对象 deferPromise.defer = Promise.deferred = function(){ let dfd = {}; dfd.promise = new Promise((resolve, reject)=>{ dfd.resolve = resolve; dfd.reject = reject; }); return dfd;}module.exports = Promise;最终测试测试当前代码是否符合Promise/A+规范全局安装 npm i -g promises-aplus-tests文件所在目录运行以下命令 (例如你的文件名为:MyPrommise.js)promise-aplus-tests MyPrommise.js等待ok.源码在github上,写文章不易,欢迎star或fork,thx~github ...

February 19, 2019 · 8 min · jiezi

【译】理解回调和Promise

理解回调和Promise原文自工程师Fernando Hernandez博客,传送门这两个概念是Javascript编程语言的基本内容。因为这种语言是在异步编程的范例下工作。所以,我决定分享这篇文章,以便了解这两个用来执行异步操作的特性——回调和Promise是什么。那么,我们开始吧!回调为了理解回调,我将做一个简短的比喻。假设我们正在通电话。在谈话时,出现了需要立即解决的情况。我们把电话挂了,我们先做需要立即解决的事情,当我们完成时,我们再回到我们刚刚暂停的电话。好吧,通过这个例子,我们可以大致了解什么是回调。现在,用编程语言说。回调是在异步操作已经完成后将要执行的功能。回调作为参数传递给异步操作。通常,是作为函数的最后一个参数传递的。这样做是一种很好的做法,所以请记住这一点。回调的结构如下所示:function sayHello() { console.log(‘Hello everyone’);}setTimeout(()=>{sayHello()}, 3000);我们在上面的例子中所做的是,首先定义一个向控制台输出消息的函数。之后,我们使用一个名为setTimeout的计时器(此计时器是一个本机Javascript函数)。此计时器是一个异步操作,在一定时间后执行回调。在这个例子中,在3000ms(3秒)之后将执行sayHello函数。回调模式正如我们在开始时提到的那样,作为优秀的开发人员,我们应该将回调位置视为参数。应始终将其作为最后一个。这就是回调模式的名称。通过这种方式,我们的代码将更具可读性,并且当其他程序员处理它时将更容易维护。我们来看另一个回调示例:const fs = require(‘fs’) // Importing Nodejs library// Declaring file pathconst filePath = ‘./users.json’// Asynchronous operation to read the filefs.readFile(filePath, function onReadFile(err, result) { // In case of error print it in the console if (err) { console.log(‘There was an error: ’ + err) return // Get out of the function } // Print on the console the file and the content of it. console.log(‘The file was successfully read it: ’ + result)})在这里,我们使用Nodejs库,用于在我们的文件系统上进行操作。在该示例中,我们使用readFile函数来从我们的计算机中读取文件。此函数接收两个参数(文件路径和回调)。我们可以注意到,名为onReadFile的回调它是最后一个参数。匿名声明回调是很常见的,但是如果会出现错误的情况,最好为它指定一个名称,以便更容易地识别它。最后,直到我们的代码完成读取所请求的文件将会执行该回调。如果存在,Javascript将在此过程中继续执行代码。回调地狱一旦你知道回调函数是如何工作的,并付诸实践,我们就必须记住一些东西。作为一名优秀的开发人员,我们必须知道如何使用它,并避免像回调地狱这样糟糕的事情。回调地狱就是滥用回调。 它看起来像这样:fs.readdir(source, function (err, files) { if (err) { console.log(‘Error finding files: ’ + err) } else { files.forEach(function (filename, fileIndex) { console.log(filename) gm(source + filename).size(function (err, values) { if (err) { console.log(‘Error identifying file size: ’ + err) } else { console.log(filename + ’ : ’ + values) aspect = (values.width / values.height) widths.forEach(function (width, widthIndex) { height = Math.round(width / aspect) console.log(‘resizing ’ + filename + ’to ’ + height + ‘x’ + height) this.resize(width, height).write(dest + ‘w’ + width + ‘_’ + filename, function(err) { if (err) console.log(‘Error writing file: ’ + err) }) }.bind(this)) } }) }) }})基本上,我们可以看到,使用嵌套回调是一种不好的做法,它会在视觉上产生一种金字塔式的效果。这将成为难以维护和读取的代码,我们不希望这样。如何避免回调地狱?命名函数:正如我之前所说,你可以做的第一件事是命名你的函数(回调)。因此,当发生错误时,它将使用函数名称以特定方式指示错误。此外,这会使你的代码更具可读性,当其他程序员阅读时,它们更容易维护它。模块化:一旦命名了函数,就可以开始单独定义它们。这样,您将只用输入回调名称。首先,可以在同一文件底部定义它们。除此之外,另一种方法是将该函数写入单独的文件。这样,我们可以在任何文件中导出和导入它。这使得我们代码更具有可重用性,有更高的可读性和易维护性。处理错误:编写代码时,我们必须记住错误总是会发生。为了能够轻松地识别定位它们,编写处理可能发生的错误的代码非常重要。通常,在回调中,错误作为第一个参数传递。我们可以通过以下方式处理错误:const fs = require(‘fs’)const filePath = ‘./users.json’fs.readFile(filePath, handleFile)function handleFile(err, result) { if (err) { return console.log(‘There was an error: ’ + err) } console.log(‘File: ’ + result)}养成良好的编程习惯,让其余程序员不会恨你一辈子!PromiseJavascript中的Promise就是相当于字面意思上的承诺。我们知道,当我们做出承诺时,这意味着我们将尽一切可能实现预期的结果。但是,我们也知道,由于某种原因,不能总是履行承诺。正如承诺在现实生活中一样,它是在Javascript中,则另一种方式表示即代码。让我们看一个Promise的例子:let promise = new Promise(function(resolve, reject) { // things to do to accomplish your promise if(/* everything turned out fine */) { resolve(‘Stuff worked’) } else { // for some reason the promise doesn’t fulfilled reject(new Error(‘it broke’)) }})Promise是Javascript的原生类(自ES6起)。promise的构造函数接收一个参数:一个回调,它有两个参数:resolvereject这些是已经在Javascript中定义的函数,因此我们不用自己去构建它们。这个具有这两个函数作为参数的回调称为执行程序。执行者在创建承诺时立即运行。执行函数将执行什么?好吧,在这里面,我们将放置所有必要的代码来实现我们的承诺。一旦执行程序完成执行,我们将发送其中一个函数作为参数。如果实现了,我们使用resolve函数。如果由于某种原因失败,我们使用reject函数。函数resolve和reject,只接收一个参数。reject函数通常会使用Error类传递错误,正如我们在前面的示例中所看到的那样。Promise有三个独特的状态:Pending:异步操作尚未完成。Fulfilled:异步操作已完成并返回一个值。Rejected:指示异步操作失败以及失败的原因。Promise对象有两个属性:State:表示Promise的状态。Result:存储Promise的值(如果已满足)或错误(如果已拒绝)。最初,Promise的状态为“pending”,结果为“undefined”。一旦promise完成执行,promise的状态和结果将被修改为相应的值。取决于promise是否已完成或被拒绝。让我们看看下面的图表来更好地理解它:一旦promise改变了他们的状态,他们就无法逆转。如何使用或调用Promise?为了使用我们创建的Promise,我们使用then和catch函数。在代码中,它们看起来像这样:promise.then(function(result) { console.log(result)}).catch(function(err) { console.log(err)})then允许我们处理已完成或已执行状态的promise函数catch将允许我们处理被拒绝状态的promise在then函数中,我们也可以处理被拒绝的promise。为此,处理程序接收两个参数。第一个是已完成的promise,第二个是被拒绝的promise。通过这种方式:promise.then(function(result) { // Handling the value console.log(result)}, function(err) { // Handling the error console.log(err)})处理程序then和catch都是异步的。基本上,一旦Javascript执行了下面的代码,就会执行then和catch。例:promise.then(function(result) { console.log(result)}).catch(function(err) { console.log(err)})console.log(‘Hello world’)我们可能认为首先它会先在控制台输出在promise中的value或error。但是要知道它们是异步操作,我们必须记住它将花费最少的时间来执行,因此消息“Hello world”还是会首先显示。Promise类有一个名为all的方法,用于执行promise数组。它看起来像这样:Promise.all([ new Promise.((resolve, reject) => setTimeout(() => resolve(1), 3000)), // 1 new Promise.((resolve, reject) => setTimeout(() => resolve(2), 2000)), // 2 new Promise.((resolve, reject) => setTimeout(() => resolve(3), 1000)), // 3]).then(result => console.log(result)) // 1, 2, 3在随后处理程序将在控制台输出每个promise的结果的数组。如果其中一个promise被reject,则该函数将被reject并出现错误。如下所示:Promise.all([ new Promise.((resolve, reject) => setTimeout(() => resolve(1), 3000)), // 1 new Promise.((resolve, reject) => setTimeout(() => resolve(2), 2000)), // 2 new Promise.((resolve, reject) => setTimeout(() => reject(new Error(‘An error has ocurred’)), 1000))]).then(result => console.log(result)).catch(err => console.log(err)) // An error has ocurred还有另一种类似于all的方法,但又有所不同。它是race方法。与all函数相同,它接收一个promise数组,但它将返回先完成或拒绝的promise。我们来看一个代码示例:let promise1 = new Promise(function(resolve, reject) { setTimeout(function() { resolve(‘promise one’) }, 3000) // Resolve after 3 seconds})let promise2 = new Promise(function(resolve, reject) { setTimeout(function() { resolve(‘promise two’) }, 1000) // Resolve after 1 seconds})Promise.race([ promise1, promise2]).then(result => console.log(result)) // promise two我们可以看到,返回给我们的值是第二个promise返回的。这是因为第一个promise是先执行的。让我们看一个被拒绝的promise的另一个例子:let promise1 = new Promise(function(resolve, reject) { setTimeout(function() { resolve(‘promise one’) }, 3000) // Resolve after 3 seconds})let promise2 = new Promise(function(resolve, reject) { setTimeout(function() { resolve(‘promise two’) }, 2000) // Resolve after 2 seconds})let promise3 = new Promise(function(resolve, reject) { setTimeout(function() { reject(‘promise three rejected’) }, 1000) // Reject after 1 second})Promise.race([ promise1, promise2, promise3]).then(result => console.log(result)).catch(err => console.log(err)) // promise three is rejected在这段代码race函数中,将要打印的是在我们声明的第三个promise中发现的错误。你可以想象得到为什么。实际上,第三个promise比其他promise先执行。因此,无论promise是否被拒绝或完成,race方法将执行第一个并忽略其他方法。到目前为止,我希望我已经让自己了解了回调和promise。基本上,Javascript的这两个特性用于处理异步操作。这就是这门语言的基础,因此它很受欢迎。 我将很快继续关于处理异步的另一篇文章——Async-Await。译者总结本文是小编的第一次译文,翻译不到位请见谅。由于突然想重温一下Promise,为此对Promise的知识点进行了再次温故,看看从不同人的角度怎么去理解Promise的。上文对Promise进行了简单的介绍并附带一些回调的知识点,也让我对回调有了新的见解。相信对读者也会有所帮助,我会再接再厉的! ...

February 18, 2019 · 3 min · jiezi

Promise的几个扩展API总结

Promise的几个扩展API总结1. Promise.none描述:和 Promise.all 相反,当所有的promise被拒绝之后,none方法执行完成的决议,如果存在一个promise执行完成的决议,none方法则执行拒绝code: Promise.none = function(promises) { return Promise.all(promises.map(promise => { return new Promise((resolve, reject) => { // Promise.all里边的所有promise实例反过来就好了 return Promise.resolve(promise).then(reject, resolve) }) })) } const promisesForNoneTest1= [ Promise.reject(‘1’), Promise.reject(‘2’), Promise.resolve(‘3’), Promise.reject(‘4’), ] Promise.none(promisesForNoneTest1).then(res => { debugger }, res => { debugger // 执行到此 }) const promisesForNoneTest2= [ Promise.reject(‘1’), Promise.reject(‘2’), Promise.reject(‘3’), Promise.reject(‘4’), ] Promise.none(promisesForNoneTest2).then(res => { debugger // 执行到此 }, res => { debugger })2. Promise.any描述:忽略被拒绝的promise,只需要有一个完成的promise,any方法就执行完成操作,如果全部的promise都被拒绝,any方法执行拒绝操作code: Promise.any = function(promises) { const result = [] return Promise.all(promises.map(promise => { // 控制Promise.all处理的所有的promise都执行reslove决议 return Promise.resolve(promise).then(res => { // 但是只记录实际上决议为resolve的结果值 result.push(res) }, () => { // 防止穿透,这里可以进行拒绝信息的返回 }) })).then(() => { return new Promise((resolve, reject) => { if (result.length > 0) resolve(result) else reject(result) }) }) } const promisesForAnyTest1= [ Promise.reject(‘1’), Promise.resolve(‘2’), Promise.reject(‘3’), Promise.resolve(‘4’), Promise.resolve(‘5’), ] Promise.any(promisesForAnyTest1).then(res => { debugger // 执行到此,res 为 [‘2’, ‘4’, ‘5’] }, res => { debugger }) const promisesForAnyTest2= [ Promise.reject(‘1’), Promise.reject(‘2’), Promise.reject(‘3’), Promise.reject(‘4’), Promise.reject(‘5’), ] Promise.any(promisesForAnyTest2).then(res => { debugger }, res => { debugger // 执行到此 })3. Promise.first描述:类似race,但是只要有一个promise决议为完成(忽略前边被拒绝的promise),就忽略后边的promisecode: Promise.first = function(promises) { return new Promise((resolve, reject) => { let rejectNum = 0 promises.forEach(promise => { // 如果当前 promise 决议为reslove,那就直接执行"根promise"的resolve // 否则去记录到拒绝的promise中,然后判断全部的promise拒绝了,执行"根promise"的reject Promise.resolve(promise).then(resolve, () => { if (++rejectNum === promises.length) { // 这里可以控制reject返回的信息 reject() } }) }) }) } const promisesForFirstTest1= [ Promise.reject(‘1’), Promise.resolve(‘2’), Promise.reject(‘3’), Promise.resolve(‘4’), ] Promise.first(promisesForFirstTest1).then(res => { debugger // 执行到此,res 为 ‘2’ }, res => { debugger }) const promisesForFirstTest2= [ Promise.reject(‘1’), Promise.resolve(‘2’), Promise.reject(‘3’), Promise.resolve(‘4’), ] Promise.first(promisesForFirstTest2).then(res => { debugger }, res => { debugger // 执行到此 })4. Promise.map描述:在第二个方法 any 中,用到了Promise.all的方法,里边使用Array.prototype.map方法处理了所有的的promise,当前这个Promise.map方法则希望把他俩弄到一起Promise.map方法希望实现一个处理批量异步操作的并行迭代方法,本质上是利用Promise.all进行二次封装code: Promise.map = function(promises, resolveCallback, rejectCallback) { return Promise.all(promises.map(promise => { return Promise.resolve(promise).then(result => { return resolveCallback(result) }, error => { return Promise.reject(rejectCallback(error)) }) })) } const promisesForMapTest1= [ Promise.resolve(‘1’), Promise.resolve(‘2’), Promise.resolve(‘3’), Promise.resolve(‘4’), ] Promise.map(promisesForMapTest1, result => { return result * 100 }, result => { return result }).then(res => { debugger // 执行到此,res为 [100, 200, 300, 400] }, res => { debugger }) const promisesForMapTest2= [ Promise.resolve(‘1’), Promise.resolve(‘2’), Promise.reject(‘3’), Promise.resolve(‘4’), ] Promise.map(promisesForMapTest2, result => { return result * 100 }, result => { return result }).then(res => { debugger }, res => { debugger // 执行到此,res为 “3” }) ...

February 13, 2019 · 2 min · jiezi

利用ES6进行Promise封装总结

原生Promise解析简介promise是异步编程的一种解决方案,比传统的解决方案–回调函数和事件–更合理和强大。promise简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,从语法上来说,Promise是一个对象,从它可以获取异步操作的消息,Promise提供统一的API,各种异步操作都可以用同样的方法进行处理特点对象的状态不受外界影响,Promise对象代表一个异步操作,有三种状态:Pendding、fulfilled、rejected。只有异步操作的结果,可以决定当前是哪一种状态,其他操作都无法改变这个状态。一旦状态改变,就不会在变,任何时候都可以得到这个结果,只有两种可能:从Pendding变为fulfilled和从Pendding变为rejected。只要这两种情况发生,状态就凝固了,会一直保持这个结果,这时就称为resolved。利用es6进行Promise封装处理同步任务原生方法调用方式 new Promise((resolve,reject)=>{ resolve(1) }).then(res=>{ console.log(res) //1 })同步封装思考 1.由调用方式可见Promise是一个类 2.它接收一个回调函数,这个回调函数接受resolve和reject方法作为参数 3.当状态改变后执行then方法,并将resolve或reject的结果作为then方法接受回调函数的参数 class Mypromise{ constructor(callback){ this.status=‘pendding’ //成功结果 this.s_res = null // 失败结果 this.f_res = null callback((arg)=>{ // 使用箭头函数this不会丢失 // 改变状态为成功 this.status = ‘fulfilled’ this.s_res = arg },(arg)=>{ // 改变状态为失败 this.status = ‘rejected’ this.f_res = arg }) } then(onresolve,onreject){ if(this.status === ‘fulfilled’){ // 当状态为成功时 onresolve(this.s_res) }else if(this.status === ‘rejected’){ // 当状态为失败时 onreject(this.f_res) } } }处理异步任务原生调用方式 new Promise((resolve,reject)=>{ setTimeOut(()=>{ resolve(1) },1000) }).then(res=>{ console.log(res) })异步封装思考 1.根据js执行机制,setTimeOut属于宏任务,then回调函数属于微任务,当主线程执行完成后,会从异步队列中 取出本次的微任务先执行。 2.也就是说,then方法执行时,状态还没有改变,所有我们需要将then方法执行的回调保存起来,等到异步代码执行 完成后,在统一执行then方法的回调函数 class Mypromise{ constructor(callback){ this.status=‘pendding’ //成功结果 this.s_res = null // 失败结果 this.f_res = null this.query = [] // ++ callback((arg)=>{ // 使用箭头函数this不会丢失 // 改变状态为成功 this.status = ‘fulfilled’ this.s_res = arg // 当状态改变后,统一执行then方法的回调 this.query.forEach(item=>{ item.resolve(arg) }) },(arg)=>{ // 改变状态为失败 this.status = ‘rejected’ this.f_res = arg // 当状态改变后,统一执行then方法的回调 this.query.forEach(item=>{ item.reject(arg) }) }) } then(onresolve,onreject){ if(this.status === ‘fulfilled’){ // 当状态为成功时 onresolve(this.s_res) }else if(this.status === ‘rejected’){ // 当状态为失败时 onreject(this.f_res) }else{ // ++ 状态没有改变 this.query.push({ // 保存回调函数到队列中 resolve:onresolve, reject:onreject }) } } } 处理链式调用原生调用方式 new Promise((resolve,reject)=>{ resolve(1) }).then(res=>{ return res }).then(res=>{ console.log(res) })链式调用思考 原生的Promise对象的then方法,返回的也是一个Promise对象,一个新的Promise才能支持链式调用 下一个then方法可以接受上一个then方法的返回值作为回调函数的参数 主要考虑上一个then方法的返回值: 1.Promise对象/具有then方法的对象 2.其他值 第一个then方法返回一个Promise对象,它的回调函数接受resFn和rejFN两个回调函数作为参数, 把成功状态的处理封装为handle函数,接受成功的结果作为参数 在handle函数,根据onresolve返回值的不同做出不同的处理 class Mypromise{ constructor(callback){ this.status=‘pendding’ //成功结果 this.s_res = null // 失败结果 this.f_res = null this.query = [] // ++ callback((arg)=>{ // 使用箭头函数this不会丢失 // 改变状态为成功 this.status = ‘fulfilled’ this.s_res = arg // 当状态改变后,统一执行then方法的回调 this.query.forEach(item=>{ item.resolve(arg) }) },(arg)=>{ // 改变状态为失败 this.status = ‘rejected’ this.f_res = arg // 当状态改变后,统一执行then方法的回调 this.query.forEach(item=>{ item.reject(arg) }) }) } then(onresolve,onreject){ return new Mypromise((resFN,rejFN)=>{ if(this.status === ‘fulfilled’){ // 当状态为成功时 handle(this.s_res) }else if(this.status === ‘rejected’){ // 当状态为失败时 errBack(this.f_res) }else{ // ++ 状态没有改变 this.query.push({ // 保存回调函数到队列中 resolve:onresolve, reject:onreject }) } function handle(value){ // 当then方法的onresolve方法有返回值时,保存其返回值,没有使用其保存的值 let returnVal = onresolve instanceof Function && onresolve(value) || value // 如果onresolve方法返回的是promise对象,则调用其then方法 if(returnVal&&returnVal[’then’] instanceof Function){ returnVal.then(res=>{ resFN(res) },err=>{ rejFN(err) }) }else{ resFN(returnVal) } } function errBack(reason){ if(onreject instanceof Function){ let returnVal = reject(reason) if(typeof returnVal !== ‘undenfined’ && returnVal[’then’] instanceof Function){ returnVal.then(res=>{ resFN(res) },err=>{ rejFN(err) }) }else{ resFN(returnVal) } }else{ rejFN(reason) } } }) } } Promise.all和Promise.race方法原生调用方式 Promise.all方法接受一个数组,数组中的每一项都是一个Promise实例,只有数组中的所有Promise实例的状态 都变为fulfilled时,此时整个状态才会变成fulfilled,此时数组中所有Promise实例的返回值组成一个新的数组, 进行传递。 Promise.race方法和Promise.all方法一样,如果不是Promise实例,就会先调用Promise.resolve方法,将参数 转为Promise实例,在进行下一步处理。 只要数组中有一个参数的状态变为fulfilled就会进行传递 // 将现有对象转换为Promise对象 Mypromise.resolve = (arg)=>{ if(typeof arg == ‘undefined’ || arg==null){ // 不带有任何参数 return new Mypromise(resolve=>{ resolve(arg) }) }else if(arg instanceof Mypromise){ // 是一个Mypromise实例 return arg }else if(arg[’then’] instanceof Function){ // 具有then方法的对象 return new Mypromise((resolve,reject)=>{ arg.then(res=>{ resolve(res) },err=>{ reject(err) }) }) }else{ // 参数不是具有then方法的对象,或根本不是对象 return new Mypromise(resolve=>{ resolve(arg) }) } } Mypromise.all = (arr)=>{ if(!Array.isArray(arr)){ throw new TypeError(‘参数必须是一个数组’) } return new Mypromise((resolve,reject)=>{ let i=0,result=[] next() functon next(){ // 如果不是Mypromise实例需要转换 Mypromise.resolve(arr[i]).then(res=>{ result.push(res) i++ if(i===arr.length){ resolve(result) }else{ next() } },reject) } }) } Mypromise.race = (arr)=>{ if(!Array.isArray(arr)){ throw new TypeError(‘参数必须是一个数组’) } return new Mypromise((resolve,reject)=>{ let done = false arr.forEach(item=>{ Mypromise.resolve(item).then(res=>{ if(!done){ resolve(res) done = true } },err=>{ if(!done){ reject(res) done = true } }) }) }) }处理Mypromise状态确定不能改变的特性 在重写callback中的resolve和reject方法执行前,先判断状态是否为’pendding’ ...

February 11, 2019 · 3 min · jiezi

实现Pormise,超级精简,一看就能明白Promise的运行原理

核心代码为了精简代码,下面的Promise实现中去掉了代码校验,这样方便小伙伴看到Promise的核心逻辑。ES6语法。活不多说,直接放代码class PromiseTest{ executor = (resolve,reject)=>{}; constructor(executor){ this.executor = executor } then(sucess,error){ function resolve(value){ sucess(value) } function reject(value){ error(value) } this.executor(resolve,reject) }}有没有很简单,下面对照最典型的实用方法非小伙伴讲解一些这个简单的代码。代码测试和代码讲解const promise = new PromiseTest(function(resolve, reject) { if (true){ resolve(“sucess”); } else { reject(“erorr”); }});promise.then(function (value) { console.log(value)}, function (error) { console.log(error)}); // => sucess简单的归纳一下,Promise构建的时候把你传入的executor(resolve, reject){}函数缓存到返回的promise对象中,当你调用promise的then方法的时候,就是执行你写的executor(resolve, reject){}函数,并then方法中定义的两个回调函数替换替换到构建Promise时定义的executor(resolve, reject){}中的两个入参。当然promise不会像我说的这么简单,但是核心原理就是这个样的。

January 25, 2019 · 1 min · jiezi

asnyc/await的并行

一直以为es7里面的async和await可以用来简化串行异步代码,而没有想到还能并行。说到底,这俩货不过是promise的语法糖,await的作用只是串行解析promise。通常我们这样写:function asyncAwaitFn(str) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(str) }, 1000); })}const parallel = async () => { //并行执行 console.time(‘parallel’) const parallelOne = await asyncAwaitFn(‘1’); const parallelTwo = await asyncAwaitFn(‘2’) console.log(parallelOne) //1 console.log(parallelTwo) //2 console.timeEnd(‘parallel’) //2003.509033203125ms}parallel()这是串行,显然最后的执行时间应该大于2000ms。但如果换一种写法:function asyncAwaitFn(str) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(str) }, 1000); })}const parallel = async () => { //并行执行 console.time(‘parallel’) const parallelOne = asyncAwaitFn(‘1’); const parallelTwo = asyncAwaitFn(‘2’) console.log(await parallelOne) //1 console.log(await parallelTwo) //2 console.timeEnd(‘parallel’) //1001.87255859375ms}parallel()最后执行时间只要1000ms,显然是并行了。不过严谨来说,这依然是promise本身的并行罢了。 ...

January 24, 2019 · 1 min · jiezi

javascript异步之Promise.all()、Promise.race()、Promise.finally()

同期异步系列文章推荐谈一谈javascript异步javascript异步中的回调javascript异步与promisejavascript异步之Promise.resolve()、Promise.reject()javascript异步之Promise then和catchjavascript异步之async(一)javascript异步之async(二)javascript异步实战javascript异步总结归档今天我们继续讨论promise网络上关于PromiseAPI使用的文章多如牛毛,为了保持javascript异步系列文章的完整性,现在对promise的API进行简单全面的介绍准备工作我在easy-mock添加了三个接口,备用依然使用axios进行ajax请求Promise.all()Promise.all()有点像“并行”我们看一个栗子<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0”> <meta http-equiv=“X-UA-Compatible” content=“ie=edge”> <title>promise</title> <script src=“https://unpkg.com/axios/dist/axios.min.js"></script></head><body> <script> { const p1 = axios.get(‘https://easy-mock.com/mock/5b0525349ae34e7a89352191/example/promise1') .then(({ data }) => { console.log(‘p1成功啦’); return data.data }) const p2 = axios.get(‘https://easy-mock.com/mock/5b0525349ae34e7a89352191/example/promise2') .then(({ data }) => { console.log(‘p2成功啦’); return data.data }) const p3 = axios.get(‘https://easy-mock.com/mock/5b0525349ae34e7a89352191/example/mock') .then(({ data }) => { console.log(‘p3成功啦’); return data.data }) const p = Promise.all([p3, p1, p2]) .then(arr => { console.log(arr); console.log(‘Promise.all成功啦’); }) .catch(err=>{ console.log(err,‘Promise.all错啦’); }) } </script></body></html>我们知道axios返回的是一个promise对象,我们可以看下 console.log(p1);Promise.all就是用于将多个 Promise 实例,包装成一个新的 Promise 实例Promise.all,接收一个数组作为参数,数组的每一项都返回Promise实例我们重点看这段代码 const p = Promise.all([p3, p1, p2]) .then(arr => { console.log(arr); console.log(‘Promise.all成功啦’); }) .catch(err=>{ console.log(err,‘Promise.all错啦’); })p1,p2,p3都是返回promise实例,Promise.all不关心他们的执行顺序,如果他们都返回成功的状态,Promise.all则返回成功的状态,输出一个数组,是这三个p1,p2,p3的返回值,数组的顺序和他们的执行顺序无关,和他们作为参数排列的顺序有关我们看下输出为了是拉长接口三的返回时间我对接口三的数据进行了修改,返回值是长度1000-2000之间的随机数组,所以p3的执行要晚于p1和p2,但我们输出的arr,p3依然在前面,这给我们带来一个便利,返回值数组的顺序和方法的执行顺序无关,可以进行人为进行控制我们将p1做一下改动,使p1报错 const p1 = axios.get(‘https://easy-mock.com/mock/5b0525349ae34e7a89352191/example/promise1') .then(({ data }) => { console.log(‘p1成功啦’); return xxxx.data//将data.data修改为xxxx.data })如果有一个返回失败(reject),Promise.all则返回失败(reject)的状态,此时第一个被reject的实例的返回值,会传递给P的回调函数。三个promise实例参数之间是“与”的关系,全部成功,Promise.all就返回成功,有一个失败,Promise.all就返回失败换个角度说,一个promise的执行结果依赖于另外几个promise的执行结果,例如:几个ajax全部执行完了,才能渲染页面,几个ajax全部执行完了,才能做一些数据的计算操作,不关心执行顺序,只关心集体的执行结果Promise.race()Promise中的竞态,用法和Promise.all类似,对应参数的要求和Promise.all相同,传入一个数组作为参数,参数要返回一个Promise实例race就是竞争的意思,数组内的Promise实例,谁执行的快,就返回谁的执行结果,不管是成功还是失败const p = Promise.race([p3, p1, p2]) .then(res => { console.log(res); console.log(‘Promise.all成功啦’); }) .catch(err=>{ console.log(err,‘Promise.all错啦’); })通过输出我们发现p1是第一个完成的,所以p的返回结果就是p1的执行结果而且就算完成,但是 进程不会立即停止,还会继续执行下去。关于race的使用场景搜了一下,很多文章都说是用来解决网络超时的提示,类似于下面这样 const p3 = axios.get(‘https://easy-mock.com/mock/5b0525349ae34e7a89352191/example/mock') .then(({ data }) => { console.log(‘p3成功啦’); return data.data }) const p4 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error(‘网络连接超时’)), 50) }) const p = Promise.race([p3, p4]) .then(res => console.log(res)) .catch(err => console.log(err));p3的ajax和50ms的定时器比较,看谁执行的快,如果超过了50ms,p3的ajax还没返回,就告知用户网络连接超时这里有个问题,就算提示超时了,p3还在继续执行,它并没有停下来,直到有状态返回个人观点:race可以用来为ajax请求的时长划定范围,如果ajax请求时长超过xxxms会执行某个方法,或者ajax请求时长不超过xxms会执行某个方法,总之,race的应用空间不是很大Promise.finally()finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。 const p = Promise.race([p3, p4]) .then(res => console.log(res)) .catch(err => console.log(err)) .finally(() => { console.log(“finally的执行与状态无关”) });当promise得到状态(不论成功或失败)后就会执行finally,原文链接参考链接Promise 对象Promise.prototype.finally ...

January 22, 2019 · 1 min · jiezi

javascript异步与promise

同期异步系列文章推荐谈一谈javascript异步javascript异步中的回调javascript异步之Promise.all()、Promise.race()、Promise.finally()javascript异步之Promise.resolve()、Promise.reject()javascript异步之Promise then和catchjavascript异步之async(一)javascript异步之async(二)javascript异步实战javascript异步总结归档我们说处理javascript异步最常用的方式就是通过回调函数,对于回调函数我们昨天对此做了介绍简单快速,我们一般使用嵌套回调或者链式回调,会产生以下问题当采用嵌套回调时,会导致层级太多,不利于维护所以我们又采用了链式回调,对嵌套回调进行拆分,拆分后的函数间耦合度很高,如果需要传递参数,函数之间的关联性会更高,而且要对参数进行校验以提高代码的健壮性如果将我们自己的回调函数传递给第三方插件或者库,就要考虑一些不可控因素调用回调过早调用回调过晚(或不被调用)调用回调次数过多或者过少promise的存在就是为了解决以上问题虽然我们日常写回调函数不会有这么严格的要求,但是如果不这样去写回调函数,就会存在隐患,当在团队协作的时候,显得编码规范显得尤为重要本文不重点介绍如何使用promise,重点介绍的是promise解决了哪些异步回调出现的问题。什么是promise我们来看一个场景,有助于我们了解promise设想一下这个场景,我去KFC,交给收银员10元,下单买一个汉堡,下单付款。到这里,我已经发出了一个请求(买汉堡),启动了一次交易。但是做汉堡需要时间,我不能马上得到这个汉堡,收银员给我一个收据来代替汉堡。到这里,收据就是一个承诺(promise),保证我最后能得到汉堡。所以我需要好好的保留的这个收据,对我来说,收据就是汉堡,虽然这张收据不能吃,我需要等待汉堡做好,等待收银员叫号通知我等待的过程中,我可以做些别的事情收银员终于叫到了我的号,我用收据换来了汉堡当然还有一种情况,当我去柜台取汉堡的时候,收银员告诉我汉堡卖光了,做汉堡的师傅受伤了等等原因,导致了我无法得到这个汉堡虽然我有收据(承诺),但是可能得到汉堡(成功),可能得不到汉堡(失败)我由等待汉堡变成了等到或者等不到,这个过程不可逆,上面很形象的介绍了promise,上面的等待汉堡和得到汉堡,汉堡卖光了,得不到汉堡,分别对应promise的三种状态三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)(一旦状态改变,就不会再变)回调函数调用过早调用过早就是将异步函数作为同步处理了,我们之前说过,javascript以单线程同步的方式执行主线程,遇到异步会将异步函数放入到任务队列中,当主线程执行完毕,会循环执行任务队列中的函数,也就是事件循环,直到任务队列为空。事件循环和任务队列事件循环就像是一个游乐场,玩过一个游戏后,你需要重新排到队尾才能再玩一次任务队列就是,在你玩过一个游戏后,可以插队接着玩我们看一个栗子 const promise = new Promise((resolve, reject) => { resolve(“成功啦”) }); promise.then(res => { console.log(res); console.log(“我是异步执行的”); }) console.log(‘我在主线程’);看下输出,重点看输出顺序//我在主线程//成功啦//我是异步执行的直接手动是promise的状态切为成功状态,console.log(“我是异步执行的”);这段代码也是异步执行的提供给then()的回调永远都是异步执行的,所以promise中不会出现回调函数过早执行的情况回调函数调用过晚或不被调用回调函数调用过晚回调函数调用过晚的处理原理和调用过早很类似,在promise的then()中存放着异步函数,所有的异步都存在于js的任务队列中,当js的主线程执行完毕后,会依次执行任务队列中的内容,不会出现执行过晚的情况回调函数不被调用我们用栗子说话 const promise = new Promise((resolve, reject) => resolve(‘成功啦’)) promise.then(s => console.log(s)); console.log(‘我在主线程’);成功状态的输出//我在主线程//成功啦成功状态下回调被调用继续看一下失败的回调 const promise = new Promise((resolve, reject) => reject(‘失败啦’)) promise.then(null, s => console.log(s)); console.log(‘我在主线程’);失败状态的输出//我在主线程//失败啦失败状态下回调被调用所以说,不管是失败还是成功,回调函数都会被调用回调函数调用次数过多或者过少调用次数过多我们之前说了promise有三种状态pending(进行中)、fulfilled(已成功)和rejected(已失败)状态一旦状态改变,就不会再变一个栗子 const promise = new Promise((resolve, reject) => { reject(‘失败啦’) resolve(‘成功啦’) }); promise.then(res => { console.log(我是异步执行的成功:${res}); },err=>{ console.log(我是异步执行的失败:${err}); }).catch(err => { console.log(err); }) console.log(‘我在主线程’);输出//我在主线程//我是异步执行的失败:失败啦当状态变为失败时,就不会再变为成功,成功的函数也不会执行,反之亦然调用次数过少回调函数正常是调用一次,过少=>0次=>回调函数不被调用,上面刚刚讨论过原文链接参考链接JavaScript Promise 迷你书Promise 对象ES6 系列之我们来聊聊 Promise ...

January 21, 2019 · 1 min · jiezi

令人费解的 async/await 执行顺序

原文发布在掘金社区:https://juejin.im/post/5c3cc981f265da616a47e028起源2019年了,相信大家对 Promise 和 async/await 都不再陌生了。前几日,我在社区读到了一篇关于 async/await 执行顺序的文章《「前端面试题系列1」今日头条 面试题和思路解析》。文中提到了一道“2017年「今日头条」的前端面试题”,还有另一篇对此题的解析文章《8张图让你一步步看清 async/await 和 promise 的执行顺序》,两文中都对问题进行了分析。不过在我看来,这两篇文章都没有把这个问题说清楚,同时在评论区中也有很多朋友留言表达了自己的疑惑。其实解决这个问题最关键的是以下两点:Promise.resolve(v) 不等于 new Promise(resolve => resolve(v))浏览器怎样处理 new Promise(resolve => resolve(thenable)),即在 Promise 中 resolve 一个 thenable 对象面试题国际惯例,先给出面试题和答案:注:执行顺序以 Chrome71 为准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 startasync1 startasync2promise1script endpromise2async1 endsetTimeout看完答案后,我与很多人一样无论如何也不理解 为什么 async1 end 会晚于promise2 输出……我的第一反应是 我对 await 的理解有偏差,所以我决心要把这个问题弄明白。本文主要解释浏览器对 await 的处理,**并一步步将原题代码转换为原生Promsie实现。所有执行顺序以 Chrome71 为准,不讨论 Babel 和 Promise 垫片。第一次发文,难免有一些不严谨之处,如有错误,还望大家在评论区批评指正!基础在解释答案之前,你需要先掌握:Promise 基础Promise 执行器中的代码会被同步调用Promise 回调是基于微任务的浏览器 eventloop宏任务与微任务的优先级宏任务的优先级高于微任务每一个宏任务执行完毕都必须将当前的微任务队列清空第一个 script 标签的代码是第一个宏任务主要内容问题主要涉及以下4点:Promise 的链式 then() 是怎样执行的async 函数的返回值await 做了什么PromiseResolveThenableJob:浏览器对 new Promise(resolve => resolve(thenable)) 的处理下面,让我们一步步将原题中的代码转换为更容易理解的等价代码。Promise 的链式 then() 是怎样执行的在正式开始之前,我们先来看以下这段代码:new Promise((r) => { r();}).then(() => console.log(1)).then(() => console.log(2)).then(() => console.log(3))new Promise((r) => { r();}).then(() => console.log(4)).then(() => console.log(5)).then(() => console.log(6))答案:142536如果你得出的答案是 1 2 3 4 5 6 那说明你还没有很好的理解 Promise.prototype.then()。为什么要先放出这段代码?因为 async/await可视为 Promise 的语法糖,同样基于微任务实现;本题主要纠结的点在于 await 到底做了什么导致 async1 end 晚于 promise2 输出。问题的关键在于其执行过程中的微任务数量,下文中我们需要用上述代码中的方式对微任务的执行顺序进行标记,以辅助我们理解这其中的执行过程。分析Promise 多个 then() 链式调用,并不是连续的创建了多个微任务并推入微任务队列,因为 then() 的返回值必然是一个 Promise,而后续的 then() 是上一步 then() 返回的 Promise 的回调传入 Promise 构造器的执行器函数内部的同步代码执行到 resolve(),将 Promise 的状态改变为 <resolved>: undefined, 然后 then 中传入的回调函数 console.log(‘1’) 作为一个微任务被推入微任务队列第二个 then() 中传入的回调函数 console.log(‘2’) 此时还没有被推入微任务队列,只有上一个 then() 中的 console.log(‘1’) 执行完毕后,console.log(‘2’) 才会被推入微任务队列总结Promise.prototype.then() 会隐式返回一个新 Promise如果 Promise 的状态是 pending,那么 then 会在该 Promise 上注册一个回调,当其状态发生变化时,对应的回调将作为一个微任务被推入微任务队列如果 Promise 的状态已经是 fulfilled 或 rejected,那么 then() 会立即创建一个微任务,将传入的对应的回调推入微任务队列为了更好的解析问题,下面我对原题代码进行一些修改,剔除和主要问题无关的代码<转换1>:async function async1() { console.log(‘async1 start’) await async2() console.log(‘async1 end’)} async function async2() { console.log(‘async2’)} async1(); new Promise((resolve) => { console.log(1) resolve()}).then(() => { console.log(2)}).then(() => { console.log(3)}).then(() => { console.log(4)})答案:async1 startasync2123async1 end4我们剔除了 setTimeout 和一些同步代码,然后为 Promise 的 then 链增加了一个回调,而最终结果中 async1 end 在 3 后输出,而不是在 2 后!await 一定是做了一些我们不理解的“诡异操作”,令其后续代码 console.log(‘async1 end’) 被推迟了2个时序。换句话说,async/await 是 Promise 的语法糖,同样基于微任务实现,不可能有其他超出我们理解的东西,所以可以断定:在 console.log(‘async1 end’) 执行前,额外执行了2个微任务,所以导致被推迟2个时序! 如果你无法理解上面这段话,没关系,请继续向下看。async 函数的返回值下面解释 async 关键字做了什么:被 async 操作符修饰的函数必然返回一个 Promise当 async 函数返回一个值时,Promise 的 resolve 方法负责传递这个值当 async 函数抛出异常时,Promise 的 reject 方法会传递这个异常值下面以原题中的函数 async2 为例,作等价转换<转换2>:function async2(){ console.log(‘async2’); return Promise.resolve();}await 操作符做了什么这里需要引入 TC39 规范:规范晦涩难懂,我们可以看看这篇文章:《「译」更快的 async 函数和 promises》,下面引入其中的一些描述:简单说,await v 初始化步骤有以下组成:把 v 转成一个 promise(跟在 await 后面的)。绑定处理函数用于后期恢复。暂停 async 函数并返回 implicit_promise 给调用者。我们一步步来看,假设 await 后是一个 promise,且最终已完成状态的值是 42。然后,引擎会创建一个新的 promise 并且把 await 后的值作为 resolve 的值。借助标准里的 PromiseResolveThenableJob 这些 promise 会被放到下个周期执行。结合规范和这篇文章,简单总结一下,对于 await v:await 后的值 v 会被转换为 Promise即使 v 是一个已经 fulfilled 的 Promise,还是会新建一个 Promise,并在这个新 Promise 中 resolve(v)await v 后续的代码的执行类似于传入 then() 中的回调如此,可进一步对原题中的 async1 作等价转换<转换3>:function async1(){ console.log(‘async1 start’) return new Promise(resolve => resolve(async2())) .then(() => { console.log(‘async1 end’) });}至此,我们根据规范综合以上所有等价转换,将 async/await 全部转换为原生 Promise 实现,其执行顺序在 Chrome71 上与一开始给出的 <转换1> 完全一致:<转换4>:function async1(){ console.log(‘async1 start’) return new Promise(resolve => resolve(async2())) .then(() => { console.log(‘async1 end’) });} function async2(){ console.log(‘async2’); return Promise.resolve();} async1(); new Promise((resolve) => { console.log(1) resolve()}).then(() => { console.log(2)}).then(() => { console.log(3)}).then(() => { console.log(4)})到了这,你是不是感觉整个思路变清晰了?不过,还是不能很好的解释 为什么 console.log(‘async1 end’) 在3后面输出,下面将说明其中的原因。PromiseResolveThenableJob:浏览器对 new Promise(resolve => resolve(thenable)) 的处理仔细观察 <转换4> 中的 async1 函数,不难发现 return new Promise(resolve => resolve(async2())) 中,Promise resolve 的是 async2(),而 async2() 返回了一个状态为 <resolved>: undefined 的 Promsie,Promise 是一个 thenable 对象。对于 thenable 对象,《ECMAScript 6 入门》中这样描述:thenable 对象指的是具有then方法的对象,比如下面这个对象let thenable = { then: function(resolve, reject) { resolve(42); }};下面需要引入 TC39 规范中对 Promise Resolve Functions 的描述:以及 PromiseResolveThenableJob:总结:对于一个对象 o,如果 o.then 是一个 function,那么 o 就可以被称为 thenable 对象对于 new Promise(resolve => resolve(thenable)),即“在 Promise 中 resolve 一个 thenable 对象”,需要先将 thenable 转化为 Promsie,然后立即调用 thenable 的 then 方法,并且 这个过程需要作为一个 job 加入微任务队列,以保证对 then 方法的解析发生在其他上下文代码的解析之后下面给出示例:let thenable = { then(resolve, reject) { console.log(‘in thenable’); resolve(100); }};new Promise((r) => { console.log(‘in p0’); r(thenable);}).then(() => { console.log(’thenable ok’) })new Promise((r) => { console.log(‘in p1’); r();}).then(() => { console.log(‘1’) }).then(() => { console.log(‘2’) }).then(() => { console.log(‘3’) }).then(() => { console.log(‘4’) });执行顺序:in p0in p1in thenable1thenable ok234解析in thenable 后于 in p1 而先于 1 输出,同时 thenable ok 在 1 后输出在执行完同步任务后,微任务队列中只有2个微任务:第一个是 转换thenable为Promise的过程,即 PromiseResolveThenableJob,第二个是 console.log(‘1’)在 PromiseResolveThenableJob 执行中会执行 thenable.then(),从而注册了另一个微任务:console.log(’thenable ok’)正是由于规范中对 thenable 的处理需要在一个微任务中完成,从而导致了第一个 Promise 的后续回调被延后了1个时序如果在 Promise 中 resolve 一个 Promise 实例呢?由于 Promise 实例是一个对象,其原型上有 then 方法,所以这也是一个 thenable 对象。同样的,浏览器会创建一个 PromiseResolveThenableJob 去处理这个 Promise 实例,这是一个微任务。在 PromiseResolveThenableJob 执行中,执行了 Promise.prototype.then,而这时 Promise 如果已经是 resolved 状态 ,then 的执行会再一次创建了一个微任务最终结果就是:额外创建了两个Job,表现上就是后续代码被推迟了2个时序最终转换上面围绕规范说了那么多,不知你有没有理解这其中的执行过程。规范是晦涩难懂的,下面我们结合规范继续对代码作“转换”,让这个过程变得更容易理解一些对于代码new Promise((resolve) => { resolve(thenable)})在执行顺序上等价于(我只敢说“在执行顺序上等价”,因为浏览器的内部实现无法简单的模拟):new Promise((resolve) => { Promise.resolve().then(() => { thenable.then(resolve) })})所以,原题中的 new Promise(resolve => resolve(async2())),在执行顺序上等价于:new Promise((resolve) => { Promise.resolve().then(() => { async2().then(resolve) })})综上,给出最终转换:<转换-END>function async1(){ console.log(‘async1 start’); const p = async2(); return new Promise((resolve) => { Promise.resolve().then(() => { p.then(resolve) }) }) .then(() => { console.log(‘async1 end’) });} function async2(){ console.log(‘async2’); return Promise.resolve();} async1(); new Promise((resolve) => { console.log(1) resolve()}).then(() => { console.log(2)}).then(() => { console.log(3)}).then(() => { console.log(4)})OK, 看到这里,你应该理解了为什么在 Chrome71 中 async1 end 在 3 后输出了。不过这还没完呢,认真的你可能已经发现,这里给出的执行顺序在 Chrome73 上不对啊。没错,这是因为 Await 规范更新了……Await 规范的更新如果你在 Chrome73 中运行这道题的代码,你会发现,执行顺序与 Chrome71 中不同,这又是为什么?我来简单说说这个事情的过程:在 Chrome71 之前的某个版本,nodejs 中有个 bug,这个 bug 的表现就是对 await 进行了激进优化,所谓激进优化,就是没有按照 TC39 规范的要求执行。V8 团队修复了这个 bug。不过,从这个 bug 中 V8 团队得到了启发,发现这个 bug 中的激进优化竟然可以带来性能提升,所以向 TC39 提交了改进方案,并会在下个版本中执行这个优化……上文中提到的译文《「译」更快的 async 函数和 promises》,说的就是这个优化的由来。激进优化文章中的“激进优化”,是指 await v 在语义上将等价于 Promise.resolve(v),而不再是现在的 new Promise(resolve => resolve(v)),所以在未来的 Chrome73 中,题中的代码可做如下等价转换:<转换-优化版本>function async1(){ console.log(‘async1 start’); const p = async2(); return Promise.resolve(p) .then(() => { console.log(‘async1 end’) });} function async2(){ console.log(‘async2’); return Promise.resolve();} async1(); new Promise((resolve) => { console.log(1) resolve()}).then(() => { console.log(2)}).then(() => { console.log(3)}).then(() => { console.log(4)})执行顺序:async1 startasync21async1 end234有没有觉得优化后的版本更容易理解了呢?还需要补充的要点Promise.resolve(v) 不等于 new Promise(r => r(v)),因为如果 v 是一个 Promise 对象,前者会直接返回 v,而后者需要经过一系列的处理(主要是 PromiseResolveThenableJob)宏任务的优先级是高于微任务的,而原题中的 setTimeout 所创建的宏任务可视为 第二个宏任务,第一个宏任务是这段程序本身总结本文从一道大家都熟悉的面试题出发,综合了 TC39 规范和《「译」更快的 async 函数和 promises》这篇文章对浏览器中的 async/await 的执行过程进行了分析,并给出了基于原生 Promise 实现的等价代码。同时,引出了即将进行的性能优化,并简单介绍了该优化的由来。我要感谢在 SF 社区中与我一同追寻答案的 @xianshenglu,以上全部分析过程的详细讨论在这里:async await 和 promise微任务执行顺序问题最后:我在偶然中看到了这个问题,由于答案令人难以理解,所以我决定搞个明白,然后便一发不可收拾……你可能会觉得这种在工作中根本不会遇到的代码没必要费这么大力气去分析,但通过以上的学习过程我还是收获了一些知识的,这颠覆了我之前对 async/await 的理解不得不说,遇到这种问题,还是得看规范才能搞明白啊…… ...

January 20, 2019 · 4 min · jiezi

Promise加载图片用法详解

现在不会用Promise都不好意思说自己是前端,Promise为什么火起来,一句话解决了回调嵌套和执行顺序问题最重要的我感觉是解决顺序问题。不过开始写之前我们先看看,promise怎么解决问题,怎么用。列举一个顺序加载图片demo://需求 加载三张图片 img1,img2,img3,加载顺序是1,2,3 let url1 = “https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3293635140,3955114282&fm=26&gp=0.jpg" let url2 = “https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1019333328,1858047370&fm=26&gp=0.jpg" let url3 = “https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4226468334,723383513&fm=26&gp=0.jpg" let oImg1 = new Image() oImg1.onload = function() { console.log(‘img1加载完毕’) let oImg2 = new Image() oImg2.onload = function() { console.log(‘img2加载完毕’) let oImg3 = new Image() oImg3.onload = function() { console.log(‘img3加载完毕’) console.log(‘全部加载完毕’) } oImg3.src = url3 } oImg2.src = url2 } oImg1.src = url1//结果没毛病接下来我们试试promise怎么做, function loadImg(url) { let img = new Image() img.src = url return new Promise((resolve, reject) => { img.onload = () => { console.log(url) resolve() } img.onerror = (e) => { reject(e) } }) } loadImg(url1).then(() => { return loadImg(url2) }).then(() => { return loadImg(url3) })接下来再看看Promise.all,//需求 加载三张图片 img1,img2 全部加载完成做一些事情 let urls = [“https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3293635140,3955114282&fm=26&gp=0.jpg”, “https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1019333328,1858047370&fm=26&gp=0.jpg” ] function loadImg(url) { let img = new Image() img.src = url return new Promise((resolve, reject) => { img.onload = () => { resolve(img) } img.onerror = (e) => { reject(e) } }) } function loadAll(arr) { let result = [] arr.forEach(item => { let p = loadImg(item).then((img) => { console.log(img) }) //存储当前Promise对象 console.log(p) result.push(p) }); Promise.all(result).then(() => { //全部完成 console.log(“done”) }).catch((err) => { // 有失败的情况 console.log(err) }); } loadAll(urls)完成。。。 ...

January 17, 2019 · 1 min · jiezi

你与弄懂promise之间可能只差这篇文章(一)

promise诞生之前:因为JS引擎在执行js代码时只分配了一个线程去执行,所以Javascript是单线程的。由于有这个前置设定,前端er在书写代码时绕不开的一件事是就是—-如何处理异步,即处理“现在和稍后”关系的问题,事实上我们每一天都在与异步逻辑打交道。在promise出现之前,前端er基本上都是通过callback的方式来解决“稍后”的问题,例如有经典的“发布-订阅”模式,观察者模式,他们都运用了传入回调函数的高阶函数。vue2.x源码在实现数据双向绑定时就是运用的发布-订阅模式。我们先来看看三个例子。(例子均在node环境中运行, 其中name.txt中的内容是"kk", age.txt中的内容是10。)1 . 回调函数(callback)。fs读取文件的先后顺序是不固定的,我们无法判断哪个文件先读取完成。此例实现的是,在完全读取两个文件的内容之后进行某个操作(例如console个啥的)。let fs = require(‘fs’);let arr = [];let after = (times, cb) => { return (data) => { arr.push(data); if (–times === 0) { cb(arr) } }}let on = after(2, (arr) => { console.log(‘我是在全部读取了2个文件内容之后打印出来的, ‘, arr)})fs.readFile(’name.txt’, ‘utf8’, (err, data) => { on(data)})fs.readFile(‘age.txt’, ‘utf8’, (err, data) => { on(data)})结果: 我是在全部读取了2个文件内容之后打印出来的, [ ‘kk’, ‘10’ ]。说明: 这种写法的问题在于,需要依靠计数来执行回调函数里面的内容。我们先得这计算出有几个异步操作,然后统计出来在全部的异步操作完成后再执行回调。2 .发布-订阅模式。订阅的时候添加订阅者,发布的时候执行相应的订阅函数。此例实现的是,在特定的时候emit了某事件,订阅了该事件的回调函数继而执行。class EventEmitter { constructor () { this.subs = {} } on (eventName, cb) { if (!this.subs[eventName]) { this.subs[eventName] = [] } this.subs[eventName].push((…args) => cb(…args)) } emit (eventName, …args) { if (this.subs[eventName]) { this.subs[eventName].forEach(cb => cb(…args)) } else { throw Error(没有订阅${eventName}这个事件) } }}const event = new EventEmitter();let fs = require(‘fs’);event.on(‘kk-event’, (…args) => { fs.readFile(’name.txt’, ‘utf8’, (err, data) => { console.log(‘data1’, data, …args) })})event.on(‘kk-event’, (…args) => { fs.readFile(‘age.txt’, ‘utf8’, (err, data) => { console.log(‘data2’, data, …args) })})event.emit(‘kk-event’, 123, 456)结果:data1 kk 123 456data2 10 123 4563 . 观察者模式。它与发布-订阅两者本质是一样的,只不过观察者模式在写法上强调观察者和被观察者之间的关系,而发布-订阅模式则没有这样的关系。此例实现的是,在被观察者的状态发生变化后,观察者执行自己的update方法进行更新。 class Subject { constructor() { this.observers = []; this.state = ‘’; // 假设观察者观察的是被观察者的state } setState (status) { // 当state变化时出发观察者的update方法 this.state = status; this.notify(); } attach (observer) { this.observers.push(observer) // 与发布-订阅不同的是,这里添加的是一个个观察者实例,这就将被观察者和观察者之间关联了起来 } notify () { this.observers.forEach(observe => observe.update()) // 在被观察者状态变化时,调用更新的是观察者的update方法 }}class Observer { constructor (name, target) { this.name = name; this.target = target; } update () { console.log(通知${this.name},被观察者状态变化,所以观察者${this.name}跟着变化) }}let fs = require(‘fs’);let subject = new Subject();let observer1 = new Observer(‘kk1’, subject);let observer2 = new Observer(‘kk2’, subject);subject.attach(observer1);subject.attach(observer2);subject.setState(‘B’);结果:通知kk1,被观察者状态变化,所以观察者kk1跟着变化通知kk2,被观察者状态变化,所以观察者kk2跟着变化 ...

January 8, 2019 · 2 min · jiezi

Promise进阶——如何实现一个Promise库

概述从上次更新Promise/A+规范后,已经很久没有更新博客了。之前由于业务需要,完成了一个TypeScript语言的Promise库。这次我们来和大家一步一步介绍下,我们如何实现一个符合Promise/A+规范的Promise库。如果对Promise/A+规范还不太了解的同学,建议先看看上一篇博客——前端基础知识储备——Promise/A+规范。实现流程首先,我们来看下,在我实现的这一个Promise中,代码由下面这几部分组成:全局异步函数执行器常量与属性类方法类静态方法通过上面这四个部分,我们就能够得到一个完整的Promise。这四个部分互相有关联,接下来我们一个一个模块来看。全局异步函数执行器在之前的Promiz的源码分析的博客中我有提到过,我们如何来实现一个异步函数执行器。通过JavaScript的执行原理我们可以知道,如果要实现异步执行相关函数的话,我们可以选择使用宏任务和微任务,这一点在Promise/A+的规范中也有提及。因此,下面我们提供了一个用宏任务来实现异步函数执行器的代码供大家参考。let index = 0;if (global.postMessage) { global.addEventListener(‘message’, (e) => { if (e.source === global) { let id = e.data; if (isRunningTask) { nextTick(functionStorage[id]); } else { isRunningTask = true; try { functionStorageid; } catch (e) { } isRunningTask = false; } delete functionStorage[id]; functionStorage[id] = void 0; } });}function nextTick(func) { if (global.setImmediate) { global.setImmediate(func); } else if (global.postMessage) { functionStorage[++index] = func; global.postMessage(index, ‘*’) } else { setTimeout(func); }}通过上面的代码我们可以看到,我们一共使用了setImmediate、postMessage、setTimeout这三个添加宏任务的方法来进行一步函数执行。常量与属性说完了如何进行异步函数的执行,我们来看下相关的常量与属性。在实现Promise之前,我们需要定义一些常量和类属性,用于后面存储数据。让我们一个一个来看下。常量首先,Promise共有5个状态,我们需要用常量来进行定义,具体如下:enum State { pending = 0, resolving = 1, rejecting = 2, resolved = 3, rejected = 4};这五个常量分别对应Promise中的5个状态,相信大家能够从名字中理解,我们就不多讲了。属性在Promise中,我们需要一些属性来存储数据状态和后续的Promise引用,具体如下:class Promise { private _value; private _reason; private _next = []; public state: State = 0; public fn; public er;}我们对属性进行逐一说明:_value,表示在resolved状态时,用来存储当前的值。_reason,表示在rejected状态时,用来存储当前的原因。_next,表示当前Promise后面跟着then函数的引用。fn,表示当前Promise中的then方法的第一个回调函数。er,表示当前Promise中的then方法的的第二个回调函数(即catch的第一个参数,下面看catch实现方法就能理解)。类方法看完了常量与类的属性,我们来看下类的静态方法。Constructor首先,如果我们要实现一个Promise,我们需要一个构造函数来初始化最初的Promise。具体代码如下:class Promise { constructor(resolver?) { if (typeof resolver !== ‘function’ && resolver !== undefined) { throw TypeError() } if (typeof this !== ‘object’) { throw TypeError() } try { if (typeof resolver === ‘function’) { resolver(this.resolve.bind(this), this.reject.bind(this)); } } catch (e) { this.reject(e); } }}从Promise/A+的规范来看,我们可以知道,如果resolver存在并且不是一个function的话,那么我们就应该抛出一个错误;否则,我们应该将resolve和reject方法传给resolver作为参数。resolve && reject那么,resolve和reject方法又是做什么的呢?这两个方法主要是用来让当前的这个Promise转换状态的,即从pending状态转换为resolving或者rejecting状态。下面让我们来具体看下代码:class Promise { resolve(value) { if (this.state === State.pending) { this._value = value; this.state = State.resolving; nextTick(this._handleNextTick.bind(this)); } return this; } reject(reason) { if (this.state === State.pending) { this._reason = reason; this.state = State.rejecting; this._value = void 0; nextTick(this._handleNextTick.bind(this)); } return this; }}从上面的代码中我们可以看到,当resolve或者reject方法被触发时,我们都改变了当前这个Proimse的状态,并且异步调用执行了_handleNextTick方法。状态的改变标志着当前的Promise已经从pending状态改变成了resolving或者rejecting状态,而相应_value和_reson也表示上一个Promise传递给下一个Promise的数据。那么,这个_handleNextTick方法又是做什么的呢?其实,这个方法的作用很简单,就是用来处理当前这个Promise后面跟着的then函数传递进来的回调函数fn和er。then && catch在了解_handleNextTick之前,我们们先看下then函数和catch函数的实现。class Promise { public then(fn, er?) { let promise = new Promise(); promise.fn = fn; promise.er = er; if (this.state === State.resolved) { promise.resolve(this._value); } else if (this.state === State.rejected) { promise.reject(this._reason); } else { this._next.push(promise); } return promise; } public catch(er) { return this.then(null, er); }}因为catch函数调用就是一个then函数的别名,我们下面就只讨论then函数。在then函数执行时,我们会创建一个新的Promise,然后将传入的两个回调函数用新的Promise的属性保存下来。然后,先判断当前的Promise的状态,如果已经是resolved或者rejected状态时,我们立即调用新的Promise中resolve或者reject方法,让下将当前Promise的_value或者_reason传递给下一个Promise,并且触发下一个Promise的状态改变。如果当前Promise的状态仍然为pending时,那么就将这个新生成的Promise保存下来,等当前这个Promise的状态改变后,再触发新的Promise变化。最后,我们返回了这个Promise的实例。handleNextTick看完了then函数,我们就可以来看下我们提到过的handleNextTick函数。class Promise { private _handleNextTick() { try { if (this.state === State.resolving && typeof this.fn === ‘function’) { this._value = this.fn.call(getThis(), this._value); } else if (this.state === State.rejecting && typeof this.er === ‘function’) { this._value = this.er.call(getThis(), this._reason); this.state = 1; } } catch (e) { this.state = State.rejecting; this._reason = e; this._value = void 0; this._finishThisTypeScriptPromise(); } // if promise === x, use TypeError to reject promise // 如果promise和x指向同一个对象,那么用TypeError作为原因拒绝promise if (this._value === this) { this.state = State.rejecting; this._reason = new TypeError(); this._value = void 0; } this._finishThisTypeScriptPromise(); }}我们先来看一个简单版的_handleNextTick函数,这样能够帮助我们快速理解Promise主流程。异步触发了_handleNextTick函数后,我们会判断当前用户处于的状态,如果当前Promise是resolving状态,我们就会调用fn函数,即我们在then函数调用时给新的Promise设置的那个fn函数;而如过当前Promise是rejecting状态,我们就会调用er函数。上面提到的getThis方法是用来获取特定的this值,具体的规范要求我们将在稍后再进行介绍。通过执行这两个同步的fn或er函数,我们能够得到当前Promise执行完传入回调后的值。在这里需要说明的是:我们在执行fn或者er函数之前,我们在_value和_reason中存放的值,是上一个Promise传递下来的值。只有当执行完了fn或者er函数后,_value和_reason中存放的值才是我们需要传递给下一个Promise的值。大家到这里可能会奇怪,我们的this指向没有发生变化,但是为什么我们的this指向的是那个新的Promise,而不是原来的那个Promise呢?我们可以从另外一个角度来看待这个问题:我们当前的这个Promise是不是由上一个Promise所产生的呢?如果是这种情况的话,我们就可以理解,在上一个Promise产生当前Promise的时候,就设置了fn和er两个函数。大家可能又会问,那么我们第一个Promise的fn和er这两个参数是怎么来的呢?那么我们就需要仔细看下上面这个逻辑了。下面我们只讨论第一个Promise处于pending的情况,其余的情况与这种情形基本相同。第一个Promise因为没有上一个Promise去设置fn和er两个参数,因此这两个参数的值就是undefined。所以在上面的逻辑中,我们已经排除了这种情况,直接进入了_finishThisTypeScriptPromise函数中。我们在这里需要特别说明下的是,有些人会认为我们在调用then函数传入的两个回调函数fn和er时,当前Promise就结束了,其实并不是这样,我们是得到了fn或者er两个函数的返回值,再将值传递给下一个Promise时,上一个Promise才会结束。关于这个逻辑我们可以看下_finishThisTypeScriptPromise函数。finishThisTypeScriptPromise_finishThisTypeScriptPromise函数的代码如下:class Promise { private _finishThisTypeScriptPromise() { if (this.state === State.resolving) { this.state = State.resolved; this._next.map((nextTypeScriptPromise) => { nextTypeScriptPromise.resolve(this._value); }); } else { this.state = State.rejected; this._next.map((nextTypeScriptPromise) => { nextTypeScriptPromise.reject(this._reason); }); } }}从_finishThisTypeScriptPromise函数中我们可以看到,我们在得到了需要传递给下一个Promise的_value或者_reason后,利用map方法逐个调用我们保存的新生成的Promise实例,调用它的resolve方法,因此我们又触发了这个Promise的状态从pending转变为resolving或者rejecting。到这里我们就已经完全了解了一个Promise从最开始的创建,到最后结束的整个生命周期。下面我们来看下在Promise/A+规范中提到的一些分支逻辑的处理情况。上一个Promise传递的value是Thenable实例首先,让我们来了解下什么是Thenable实例。Thenable实例指的是属性中有then函数的对象。Promise就是的一种特殊的Thenable对象。下面,为了方便讲解,我们将用Promise来代替Thenable进行讲解,其他的Thenable类大家可以参考类似思路进行分析。如果我们在传递给我们的_value中是一个Promise实例,那么我们必须在等待传入的Promise状态转换到resolved之后,当前的Promise才能够继续往下执行,即我们从传入的Promise中得到了一个非Thenable返回值时,我们才能用这个值来调用属性中的fn或者er方法。那么,我们要怎么样才能获取到传入的这个Promise的返回值呢?在Promise中其实用到了一个非常巧妙的方法:因为传入的Promise中有一个then函数(Thenable定义),因此我们就调用then函数,在第一个回调函数fn中传入获取_value,触发当前的Promise继续执行。如果是触发了第二个回调函数er,那么就用在er中得到的_reason来拒绝掉当前的Promise。具体判断逻辑如下:class Promise { private _handleNextTick() { let ref; let count = 0; try { // 判断传入的this._value是否为一个thanable // check if this._value a thenable ref = this._value && this._value.then; } catch (e) { this.state = State.rejecting; this._reason = e; this._value = void 0; return this._handleNextTick(); } if (this.state !== State.rejecting && (typeof this._value === ‘object’ || typeof this._value === ‘function’) && typeof ref === ‘function’) { // add a then function to get the status of the promise // 在原有TypeScriptPromise后增加一个then函数用来判断原有promise的状态 try { ref.call(this._value, (value) => { if (count++) { return; } this._value = value; this.state = State.resolving; this._handleNextTick(); }, (reason) => { if (count++) { return; } this._reason = reason; this.state = State.rejecting; this._value = void 0; this._handleNextTick(); }); } catch (e) { this.state = State.rejecting; this._reason = e; this._value = void 0; this._handleNextTick(); } } else { try { if (this.state === State.resolving && typeof this.fn === ‘function’) { this._value = this.fn.call(getThis(), this._value); } else if (this.state === State.rejecting && typeof this.er === ‘function’) { this._value = this.er.call(getThis(), this._reason); this.state = 1; } } catch (e) { this.state = State.rejecting; this._reason = e; this._value = void 0; this._finishThisTypeScriptPromise(); } this._finishThisTypeScriptPromise(); } }}promise === value在Promise/A+规范中,如果返回的_value值等于用户自身时,需要用TypeError错误拒绝掉当前的Promise。因此我们需要在_handleNextTick中加入以下判断代码:class Promise { private _handleNextTick() { let ref; let count = 0; try { // 判断传入的this._value是否为一个thanable // check if this._value a thenable ref = this._value && this._value.then; } catch (e) { this.state = State.rejecting; this._reason = e; this._value = void 0; return this._handleNextTick(); } if (this.state !== State.rejecting && (typeof this._value === ‘object’ || typeof this._value === ‘function’) && typeof ref === ‘function’) { // add a then function to get the status of the promise // 在原有TypeScriptPromise后增加一个then函数用来判断原有promise的状态 … } else { try { if (this.state === State.resolving && typeof this.fn === ‘function’) { this._value = this.fn.call(getThis(), this._value); } else if (this.state === State.rejecting && typeof this.er === ‘function’) { this._value = this.er.call(getThis(), this._reason); this.state = 1; } } catch (e) { this.state = State.rejecting; this._reason = e; this._value = void 0; this._finishThisTypeScriptPromise(); } // if promise === x, use TypeError to reject promise // 如果promise和x指向同一个对象,那么用TypeError作为原因拒绝promise if (this._value === this) { this.state = State.rejecting; this._reason = new TypeError(); this._value = void 0; } this._finishThisTypeScriptPromise(); } }}getThis在Promise/A+规范中规定:我们在调用fn和er两个回调函数时,this的指向有限制。在严格模式下,this的值应该为undefined;在宽松模式下时,this的值应该为global。因此,我们还需要提供一个getThis函数用于处理上述情况。具体代码如下:class Promise { …}function getThis() { return this;}类静态方法我们通过上面说到的类方法和一些特定分支的逻辑处理,我们就已经实现了一个符合基本功能的Promise类。那么,下面我们来看下ES6中提供的一些标准API我们如何来进行实现。具体API如下:resolverejectallrace下面我们就一个一个方法来看下。resolve && reject首先我们来看下最简单的resolve和reject方法。class Promise { public static resolve(value?) { if (TypeScriptPromise._d !== 1) { throw TypeError(); } if (value instanceof TypeScriptPromise) { return value; } return new TypeScriptPromise((resolve) => { resolve(value); }); } public static reject(value?) { if (TypeScriptPromise._d !== 1) { throw TypeError(); } return new TypeScriptPromise((resolve, reject) => { reject(value); }); }}通过上面代码我们可以看到,resolve和reject方法基本上就是直接使用了内部的constructor方法进行Promise构建。allclass Promise { public static all(arr) { if (TypeScriptPromise._d !== 1) { throw TypeError(); } if (!(arr instanceof Array)) { return TypeScriptPromise.reject(new TypeError()); } let promise = new TypeScriptPromise(); function done() { // 统计还有多少未完成的TypeScriptPromise // count the unresolved promise let unresolvedNumber = arr.filter((element) => { return element && element.then; }).length; if (!unresolvedNumber) { promise.resolve(arr); } arr.map((element, index) => { if (element && element.then) { element.then((value) => { arr[index] = value; done(); return value; }); } }); } done(); return promise; }}下面我们根据上面的代码来简单说下all函数的基本思路。首先我们需要先创建一个新的Promise用于返回,保证后面用户调用then函数进行后续逻辑处理时可以设置新Promise的fn和er这两个回调函数。然后,我们怎么获取上面Promise数组中每一个Promise的值呢?方法很简单,我们在前面就已经介绍过:我们调用了每一个Promise的then函数用来获取当前这个Promise的值。并且,在每个Promise完成时,我们都检查下是否所有的Promise都已经完成,如果已经完成,则触发新Promise的状态从pending转换为resolving或者rejecting。raceclass Promise { public static race(arr) { if (TypeScriptPromise._d !== 1) { throw TypeError(); } if (!(arr instanceof Array)) { return TypeScriptPromise.reject(new TypeError()); } let promise = new TypeScriptPromise(); function done(value?) { if (value) { promise.resolve(value); } let unresolvedNumber = arr.filter((element) => { return element && element.then; }).length; if (!unresolvedNumber) { promise.resolve(arr); } arr.map((element, index) => { if (element && element.then) { element.then((value) => { arr[index] = value; done(value); return value; }); } }); } done(); return promise; }}race的思路与all基本一致。只是我们在处理函数上不同。当我们只要检测到数组中的Promise有一个已经转换到了resolve或者rejected状态(通过没有then函数来进行判断)时,就会立即出发新创建的Promise示例的状态从pending转换为resolving或者rejecting。总结我们对Promise的异步函数执行器、常量与属性、类方法、类静态方法进行了逐一介绍,让大家对整个Promise的构造和声明周期有了一个深度的理解和认知。在整个开发中需要注意到的一些关键点和细节,我在上面也一一说明了。大家只需要按照这个思路,对照Promise/A+规范就能够完成一个符合规范的Promise库。最后,给大家提供一个Promise/A+测试工具,大家实现了自己的Promise后,可以使用这个工具来测试是否完全符合整个Promise/A+规范。当然,大家如果想使用我的现成代码,也欢迎大家使用我的代码Github/typescript-proimse。 ...

January 2, 2019 · 5 min · jiezi

[ 造轮子 ] 手动封装 AJAX (三) —— 最终版

导言在开始之前先想一想ajax是怎样的流程首先打开一个连接发送数据返回结果我们要自定义的设置有哪些设置请求方式设置请求头设置返回数据格式返回成功后或失败后我们要做的功能有哪些数据校验统一数据的格式支持文件上传对于传入参数的容错处理经过以上思考基本结构大致成型数据校验数据格式的统一建立连接设置请求头设置返回数据格式发送数据返回成功或失败代码如下class AJAX { constructor({url = “",method = “GET”,data = {},async = true,success,error,resType = “",headers = {}}) { //集中管理传递过来的参数 this.option = {url,method,data,async,success,error,resType,headers}; this.xhr = new XMLHttpRequest(); this.start(); } start() { //数据校验 this.checkOption(); //数据格式的统一 this.initOption(); //建立连接 this.open(); //设置请求头 this.setHeaders(); //设置返回数据格式 this.setResponseType(); //发送数据 this.sendData() //返回成功或失败 this.responseData(); }; }接下来添加校验功能首先url不能是空然后请求头必须是字面量对象格式 {key:value}再有就是一些简单的警告代码如下checkOption() { let {url,async,resType,headers} = this.option; if (url === ‘’) { throw new Error(‘请求地址不能为空’); //打印错误信息,并停止当前进程 //Console.error(‘请求地址为空’); 也可以打印错误信息,但是不能停止当前进程 } if(typeof headers !== ‘object’){ throw new Error(‘设置请求头时请传入 {key:value,key:value…} 的格式’); } if(typeof resType !== ‘string’){ throw new Error(‘设置返回数据格式时请传入字符出串格式’); } if (typeof url !== ‘string’) { //输出警告信息 console.warn(‘当前请求地址不是字符串,现在将其尝试转换为字符串’); } if (async === false && resType != ‘’) { console.warn(‘如果设置了请求方式为同步,即使设置了返回数据格式也不会生效’); }};需要注意的是返回数据格式可以设置这几个值,之后会写一个详细的传参指南接下来是数据的处理首先我们需要保证请求格式,不管传入时是大写还是小写,在我们设置请求格式时要是全部大写还有就是url可能是数字的,需要转换成字符为了方便将 async不是布尔型的转成布尔型,这是什么概念,就是传参时 写数字 1 是异步 数字 0 是同步将需要发送的内容做一个处理initOption() { let {url,async,method} = this.option; //url不是字符串转换成字符串 if (typeof url !== ‘string’) { try { this.option.url = url.toString(); console.log(转换成功: "${this.option.url}"); } catch (error) { throw new Error(‘url 转换字符串失败’); } } //async不是布尔型转成布尔型 if(typeof async !==‘boolean’){ async == true ? this.option.async = true : this.option.async = false; } //将 post get 转换为大写 this.option.method = method.toUpperCase(); //post和get数据初始化 if(this.option.method != ‘FORMDATA’){// [1] let data = this.option.data; if(typeof data === ‘object’){//[2] if( this.option.method === ‘GET’){ let arr=[]; for(let name in data){ arr.push(${name}=${data[name]});//[3] } let strData=arr.join(’&’);//[4] this.option.data=?${strData};//[5] }else if( this.option.method === ‘POST’){ let formData = new FormData();//[6] for(let key in data){ formData.append(${key},${data[key]}); } this.option.data=formData; } }else if(typeof data === ‘string’ && this.option.method === ‘GET’){//[7] this.option.data=?${data}; } }};这里详细说说对需要发送数据的处理,按照序号来说判断它不是 formData ,也就是说是 GET 和 POST 时我们进行数据处理,是 formData 不进行处理,直接发送,这是为了能够实现文件上传功能判断它是不是 {key:vlue} 这种格式的,是的话解析或拼接,不是的话跳到 [7] 如果是字符串直接加到 url 后边[3] [4] [5] 这里是为了处理成 url?key=value$key=value 这种 url 传参的数据格式[6] 是新建了一个 FormData 对象,是 ajax2.0 里边的,它最主要的可以用 ajax 实现文件上传功能,在这里是为了代码简单打开连接经过之前的数据处理这里只需要判断下是 GET 还是其他方式(post formdata),然后选择对应的连接方式 open(){ let {method,url,async,data} = this.option; if(method === ‘GET’){ this.xhr.open(method,url+data,async); }else{ this.xhr.open(method,url,async); } }设置自定义请求头将传入的参数进行解析,然后设置自定义请求头代码如下setHeaders(){ let headers = this.option.headers; for(let key in headers){ this.xhr.setRequestHeader(${key.toString()},${headers[key].toString()}) } }设置返回数据格式、发送数据由于同步请求时不能设置返回数据格式,所以做下判断发送数据这里,在经过之前的数据处理后只有 GET 方式有所区别,其他两种没有区别(支持 GET POST 以及我自己定义的一种,更多请求方法可自行扩展)setResponseType() { if (this.option.async) { this.xhr.responseType = this.option.resType; }}sendData(){ if(this.option.method == ‘GET’){ this.xhr.send(); }else{ this.xhr.send(this.option.data); }}请求完成后的数据返回请求完成后会返回数据 判断 success 以及 error 是不是函数,是的话会将数据返回给 success 或者将错误信息返回给 errorresponseData(){ this.xhr.onload = ()=>{ if(this.xhr.status >= 200 && this.xhr.status < 300 || this.xhr.status === 304){ typeof this.option.success === ‘function’ && this.option.success(this.xhr.response); }else{ typeof this.option.error === ‘function’ && this.option.error(this.xhr.statusText); } }}在实现基本功能后,突然想到 jQuery 的 ajax 是会返回一个 promise 对象,可以同时使用回掉函数,或者使用 then 和 catch 来处理数据 因此修改了下传入参数,以及返回数据的处理传参时代码如下//add resolve rejectclass AJAX { constructor({url = “",method = “GET”,data = {},async = true,success,error,resType = “",headers = {},resolve,reject}) { this.option = {url,method,data,async,success,error,resType,headers,resolve,reject}; this.xhr = new XMLHttpRequest(); this.start(); }}返回数据时代码如下responseData(){ this.xhr.onload = ()=>{ if(this.xhr.status >= 200 && this.xhr.status < 300 || this.xhr.status === 304){ typeof this.option.success === ‘function’ && this.option.success(this.xhr.response); this.option.resolve(this.xhr.response);//add }else{ typeof this.option.error === ‘function’ && this.option.error(this.xhr.statusText); this.option.reject(this.xhr.statusText);//add } }}最终代码class AJAX { constructor({url = “",method = “GET”,data = {},async = true,success,error,resType = “",headers = {},resolve,reject}) { this.option = {url,method,data,async,success,error,resType,headers,resolve,reject}; this.xhr = new XMLHttpRequest(); this.start(); } start() { //数据校验 this.checkOption(); //数据格式的统一 this.initOption(); //建立连接 this.open(); //设置请求头 this.setHeaders(); //设置返回数据格式 this.setResponseType(); //发送数据 this.sendData() //返回成功或失败 this.responseData(); }; checkOption() { let {url,async,resType,headers} = this.option; if (url === ‘’) { throw new Error(‘请求地址不能为空’); //打印错误信息,并停止当前进程 //Console.error(‘请求地址为空’); 也可以打印错误信息,但是不能停止当前进程 } if(typeof headers !== ‘object’){ throw new Error(‘设置请求头时请传入 {key:value,key:value…} 的格式’); } if(typeof resType !== ‘string’){ throw new Error(‘设置返回数据格式时请传入字符出串格式’); } // "” 与设置为"text"相同, 是默认类型 (实际上是 DOMString) // “arraybuffer” 将接收到的数据类型视为一个包含二进制数据的 JavaScript ArrayBuffer // “blob” 将接收到的数据类型视为一个包含二进制数据的 Blob 对象 // “document” 将接收到的数据类型视为一个 HTML Document 或 XML XMLDocument ,这取决于接收到的数据的 MIME 类型 // “json” 将接收到的数据类型视为 JSON 解析得到的 // “text” 将接收到的数据类型视为包含在 DOMString 对象中的文本 if (typeof url !== ‘string’) { //输出警告信息 console.warn(‘当前请求地址不是字符串,现在将其尝试转换为字符串’); } if (async === false && resType != ‘’) { console.warn(‘如果设置了请求方式为同步,即使设置了返回数据格式也不会生效’); } }; initOption() { let {url,async,method} = this.option; //url不是字符串转换成字符串 if (typeof url !== ‘string’) { try { this.option.url = url.toString(); console.log(转换成功: "${this.option.url}"); } catch (error) { throw new Error(‘url 转换字符串失败’); } } //async不是布尔型转成布尔型 if(typeof async !==‘boolean’){ async == true ? this.option.async = true : this.option.async = false; } //将 post get 转换为大写 this.option.method = method.toUpperCase(); //post和get数据初始化 if(this.option.method != ‘FORMDATA’){ let data = this.option.data; if(typeof data === ‘object’){ if( this.option.method === ‘GET’){ let arr=[]; for(let name in data){ arr.push(${name}=${data[name]}); } let strData=arr.join(’&’); this.option.data=?${strData}; }else if( this.option.method === ‘POST’){ let formData = new FormData(); for(let key in data){ formData.append(${key},${data[key]}); } this.option.data=formData; } }else if(typeof data === ‘string’ && this.option.method === ‘GET’){ this.option.data=?${data}; } } }; open(){ let {method,url,async,data} = this.option; if(method === ‘GET’){ this.xhr.open(method,url+data,async); }else{ this.xhr.open(method,url,async); } } setHeaders(){ let headers = this.option.headers; for(let key in headers){ this.xhr.setRequestHeader(${key.toString()},${headers[key].toString()}) } } setResponseType() { if (this.option.async) { this.xhr.responseType = this.option.resType; } } sendData(){ if(this.option.method == ‘GET’){ this.xhr.send(); }else{ this.xhr.send(this.option.data); } } responseData(){ this.xhr.onload = ()=>{ if(this.xhr.status >= 200 && this.xhr.status < 300 || this.xhr.status === 304){ typeof this.option.success === ‘function’ && this.option.success(this.xhr.response); this.option.resolve(this.xhr.response); }else{ typeof this.option.error === ‘function’ && this.option.error(this.xhr.statusText); this.option.reject(this.xhr.statusText); } } } all(promises) { return Promise.all(promises); };}function ajax({url,method,data,async,success,error,resType,headers}){ return new Promise((resolve, reject) => { return new AJAX({url,method,data,async,success,error,resType,headers,resolve,reject}); });}使用时可以将代码复制粘贴到单独的 js 文件然后用 script 标签引入 也可以添加一行 export 代码将最后的 ajax 暴露出去 使用import 引入 具体使用方法用法ajax({ url:‘api/login’, method:‘post’,//支持 GET POST 和我自定义的 FORMDATA ,传入时不区分大小写 data = { name:“yhtx”, id:“1997” },//除了这种还支持字符串 “name=yhtx&id=1997”;以及 formData 数据,在传入formData 数据时请将 method 设置为 FORMDATA async = true,//可以使用数字 1 代替 true ,数字 0 代替 false success(res){ //可以使用回调的形式处理数据也可以使用 then },error(err){ //可以使用回调的形式处理错误也可以使用 catch }, resType = “”,//可以传入 "” “arraybuffer” “blob” “document” “json” “text” headers = { mycookie: “46afqwiocibQEIJfa498./&678” //使用对象的方式传参 }}).then((res)=>{ //可以使用 then 的形式处理数据也可以使用回调函数}).catch((err)=>{ //可以使用 catch 的形式处理数据也可以使用回调函数}) ...

December 30, 2018 · 4 min · jiezi

学习并实现一个Promise

学习自阮一峰老师 ECMAScript 6 入门 Promise介绍参考 promise-实现 Promise属于微任务,而模拟的 Promise 都是使用setTimeout,属于宏任务。所以在某些情况下会有bug,需要注意,如:setTimeout(‘console.log(1)’)new Promise(resolve=>{ console.log(2) resolve()}).then(=>{ console.log(3)})正确的结果应该是 2 3 1,但是模拟的 Promise 返回 1 2 3。bluebird,es6-promise等也有这个问题。代码实现// 三种状态 + 1var PENDING = “pending”;var RESOLVED = “resolved”;var REJECTED = “rejected”;var FINALLY = “finally”;var Promise = window.Promise || function(){ var Promise = function(fn){ var that = this; that.currentState = PENDING; that.value = undefined; // 用于保存 then 中的回调,只有当 promise // 状态为 pending 时才会缓存,并且每个实例至多缓存一个 that.Callbacks = [] //这个函数是封装的resolve,reject的公共方法,这个方法不执行,then、catch、finally中的回都不执行 var stateF = function(state, value){ setTimeout(function(){ // 异步执行,保证执行顺序 if (that.currentState === PENDING) { that.value = value; var cb; //回调状态,默认为Promise状态 //通过修改回调状态,来确定运行哪个回调 //如没有抛出错误,则不运行 REJECTED 回调,抛出错误,则catch之前的 RESOLVED 回调不运行 var cbState = that.currentState = state; while(cb = that.Callbacks.shift()){ //状态为 RESOLVED,运行then回调,状态为 REJECTED,运行catch回调 if(cb.state == cbState || cb.state == FINALLY){ try{ if(cb.state == FINALLY){ //状态为 finally 的回调不接受参数,不返回参数 cb.fn() }else{ //运行正确,回调状态变为 RESOLVED cbState = RESOLVED //该回调的返回值是下一个 then 回调的参数 that.value = cb.fn(that.value) } }catch(err){ //运行错误,回调状态变为 REJECTED cbState = REJECTED //该错误是下一个 catch 回调的参数 that.value = err } } } } }) } var resolve = function(value){ if (value instanceof Promise) { // 如果 value 是个 Promise,递归执行 //等 Promise 状态返回后,重新运行 resolve 或 reject return value.then(resolve, reject) } stateF(RESOLVED, value) } var reject = function (reason) { stateF(REJECTED, reason) } // new Promise(() => throw Error(’error)) try { fn(resolve, reject); } catch (e) { reject(e); } } /* Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 const p = Promise.all([p1, p2, p3]); 上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。 Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。 * / Promise.all = function(arr){ arr = Array.prototype.slice.apply(arr) var values = [], count = 0; var p = new Promise(function(resolve, reject){ arr.forEach(function(item, index){ Promise.resolve(item).then(function(value){ values[ index ] = value; count++ if(p.currentState === PENDING && count === arr.length){ resolve(values) } }).catch(function(e){ p.currentState === PENDING && reject(e) }) }) }) return p } / Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。 const p = Promise.race([p1, p2, p3]); 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。 * / Promise.race = function(arr){ arr = Array.prototype.slice.apply(arr) var p = new Promise(function(resolve, reject){ arr.forEach(function(item, index){ Promise.resolve(item).then(function(value){ p.currentState === PENDING && resolve(value) }).catch(function(e){ p.currentState === PENDING && reject(e) }) }) }) return p } / 将现有对象转为 Promise 对象 如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。 参数是一个具有then方法的对象,Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。 如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。 Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。 * / Promise.resolve = function(value){ if (value instanceof Promise) { return value } var fn = value.toString() === “[object Object]” && typeof value.then === ‘function’ ? value.then : function(resolve){ resolve(value) }; return new Promise(fn) } / Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。 Promise.reject()方法的参数,会原封不动地作为reject的参数,变成后续方法的参数。这一点与Promise.resolve方法不一致 * / Promise.reject = function(value){ return new Promise(function(reject){ reject(value) }) } //提取的公共方法,then,catch,finally共用 function bindF(state, fn){ if(typeof fn === ‘function’){ if(this.currentState === PENDING){ this.Callbacks.push({ state: state, fn: fn }) }else{ //状态改变后,不是REJECTED,直接运行 state !== REJECTED && fn(this.value) } } return this } / then方法的作用是为 Promise 实例添加状态改变时的回调函数。 then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。 * / Promise.prototype.then = function(resolve, reject){ bindF.call(this, RESOLVED, resolve) return bindF.call(this, REJECTED, reject) } / Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名 用于指定发生错误时的回调函数。 * / Promise.prototype.catch = function(reject){ return bindF.call(this, REJECTED, reject) } / finally方法用于指定不管 Promise 对象状态是 resolve 或 reject ,都会执行的操作。 * */ Promise.prototype.finally = function(fina){ return bindF.call(this, FINALLY, fina) } return Promise}()if (typeof Promise.try !== ‘function’) { Promise.try = function(fn){ return new Promise(function (resolve) { resolve(fn()); }); }}最后的 Promise.try实际开发中,经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。 一般的,为了确保同步函数同步执行,异步函数异步执行,我们会这么做: const f1 = () => new Promise(resolve=>{ setTimeout(=>{ resolve([1,2,3]) },1000) }); const f2 = _ => ({a: 1}); new Promise( resolve => resolve(f1()) ).then(data=>{console.log(data)}) new Promise( resolve => resolve(f2()) ).then(data=>{console.log(data)}) //{a: 1} //[1, 2, 3]同时,这么写还可以更好的处理错误。如果同步或者异步方法本身是错误的,我们需在外层套一层try来处理,这种写法则可以省略try,使代码更美观。正对这种写法,现在已经有提案,提供Promise.try方法替代上面的写法。 ...

December 29, 2018 · 3 min · jiezi

promise, async, await, execution order

async can be transformed to promise. So, if we want to understand async, we have to understand promise first.PromiseNormally, promise is easy to understand, especially when using like this:promise .then(() => { // }) .then(() => { // }) .then(() => { // })then after then would make the order of async callbacks clear. Actually we shouldn’t rely on async callbacks order. But a certain execution order would make me feel more comfortable. However, sometimes, things are different.RESOLVE and Promise.resolve()Normally, I initialize a promise by Promise.resolve() because it seems too troublesome to use Promise constructor like below which I called it RESOLVE in this article.new Promise((resolve,reject)=>{ resolve()})And normally, I used it by Promise.resolve(non-thenable) which is equivalent to RESOLVE(non-thenable)new Promise((resolve, reject) => { resolve(non-thenable)})So, it doesn’t matter which one you choose. RESOLVE(non-thenable) or Promise.resolve(non-thenable). However, when it comes to thenable, things are different. Promise.resolve(thenable) is not equivalent to RESOLVE(thenable)new Promise((resolve, reject) => { resolve(thenable)})I explained it carefully in What’s the difference between resolve(promise) and resolve(’non-thenable-object’)?. And here is the conclusion:for non-thenable, Promise.resolve(non-thenable) is equivalent to RESOLVE(non-thenable)for thenable, Promise.resolve(thenable) is not equivalent to RESOLVE(thenable) because RESOLVE(thenable)new Promise((resolve, reject) => { resolve(thenable)})is equivalent tonew Promise((resolve, reject) => { Promise.resolve().then(() => { thenable.then(resolve) })})according to spec. It’s obviously not equivalent to Promise.resolve(thenable). You can test it by this example:let p1 = Promise.resolve(1)Promise.resolve(p1).then(res => { console.log(res)})p1.then(res => { console.log(2)})//1//2whilelet p1 = Promise.resolve(1)new Promise((resolve, reject) => { resolve(p1)}).then(res => { console.log(res)})p1.then(res => { console.log(2)})//2//1So, here comes another question. When would we use Promise.resolve(thenable) or RESOLVE(thenable)? It doesn’t seem to be that common.Yes, indeed. Except async and await.async and awaitAs we all know or spec says that the result of async returns promise. For example:(async function(){}()).toString() //"[object Promise]“And await can be used in async.awaitSo, how does await work in async? According to spec:Await:We can transform await codeconst p1 = Promise.resolve(1)const async1 = async function() { const res1 = await p1 console.log(res1)}async1()p1.then(() => console.log(‘after gen’))toconst p1 = Promise.resolve(1)const async1 = async function() { new Promise(resolve => { resolve(p1) }).then(res => { const res1 = res console.log(res1) })}async1()p1.then(() => console.log(‘after gen’))The result is the same:after gen1on chrome 70. However, in chrome canary 73 the former result is1after genWhy? The reason can be found in https://github.com/tc39/ecma2… Simply say, the spec to await was going to change to:What’s difference? The difference is exactly the difference between RESOLVE(thenable) and Promise.resolve(thenable).In the past and chrome 70, we are using RESOLVE(thenable), so I transformed the code like above. If using this new spec, the code should be transformed toconst p1 = Promise.resolve(1)const async1 = async function() { Promise.resolve(p1).then(res => { const res1 = res console.log(res1) })}async1()p1.then(() => console.log(‘after gen’))In this case, chrome 70 and canary 73 would all get1after genThat’s the spec change for await. Hope you both understand the way before and after change.asyncNow, let’s talk about async. How does async work? According to spec:The spawn used in the above desugaring is a call to the following algorithm. This algorithm does not need to be exposed directly as an API to user code, it is part of the semantics of async functions.And the spawn isfunction spawn (genF, self) { return new Promise(function (resolve, reject) { var gen = genF.call(self) function step (nextF) { var next try { next = nextF() } catch (e) { // finished with failure, reject the promise reject(e) return } if (next.done) { // finished with success, resolve the promise resolve(next.value) return } // not finished, chain off the yielded promise and step again Promise.resolve(next.value).then( function (v) { step(function () { return gen.next(v) }) }, function (e) { step(function () { return gen.throw(e) }) } ) } step(function () { return gen.next(undefined) }) })}However, I think the spawn is the future version which doesn’t apply to chrome 70 because it used Promise.resolve(next.value) instead of RESOLVE(next.value) to transform await. So, I thought the old version or version applied to chrome 70 should befunction spawn (genF, self) { return new Promise(function (resolve, reject) { var gen = genF.call(self) function step (nextF) { var next try { next = nextF() } catch (e) { // finished with failure, reject the promise reject(e) return } if (next.done) { // finished with success, resolve the promise resolve(next.value) return } // not finished, chain off the yielded promise and step again /* modified line / new Promise(resolve => resolve(next.value)).then( / origin line / // Promise.resolve(next.value).then( function (v) { step(function () { return gen.next(v) }) }, function (e) { step(function () { return gen.throw(e) }) } ) } step(function () { return gen.next(undefined) }) })}You can tested it by comparing the result of below example.const p1 = Promise.resolve(1)const p2 = Promise.resolve(2)const async1 = async function () { const res1 = await p1 console.log(res1) const res2 = await p2 console.log(res2)}async1()p1.then(() => console.log(‘after gen’))withconst p1 = Promise.resolve(1)const p2 = Promise.resolve(2)const gen = function () { const res1 = yield p1 console.log(res1) const res2 = yield p2 console.log(res2)}const async1Eq = function () { spawn(gen, this)}async1Eq()p1.then(() => console.log(‘after gen’))The result would be:On chrome 70, with the former spawn, you will get the different result. While you will get the same result with the latter spawn.In the same way, on chrome 73, with the former spawn, you will get the same result. While you will get the different result with the latter spawn.Origin Post ...

December 27, 2018 · 5 min · jiezi

js 任务

前言先看一段代码setTimeout(function() { console.log(’timeout’);}, 0);new Promise((resolve, reject) => { console.log(‘promise’); resolve();}).then(function() { console.log(’then’);});console.log(‘global’);输出: promise 、global、 then、timeoutmacrotasks 和 microtasks在 V8 实现中包含两种任务:macrotasksscript ,setTimeout, setInterval, setImmediate, I/O, UI renderingmicrotasksprocess.nextTick, Promises, Object.observe, MutationObserver执行过程如下:JavaScript 引擎首先从 macrotask queue 中取出第一个任务,执行完毕后,将microtask queue中的所有任务取出,按顺序全部执行;浏览器进行渲染视图然后再从 macrotask queue 中取下一个,执行完毕后,再次将 microtask queue 中的全部取出;循环往复,直到两个 queue 中的任务都取完。注意:从上面可以看到,microtask 会执行完毕才进行渲染,如果 microtask 执行时间教长,会导致卡顿上面执行过程只是 chrome 的,safri 又不太一样执行过程的示例 :https://jakearchibald.com/201…如何模拟 Promise.thennew MutationObserver(function() { console.log(‘mutate’);}).observe(document.body, { attributes: true});document.body.setAttribute(‘data-random’, Math.random());setTimeout(function() { console.log(’timeout’);}, 0);new Promise((resolve, reject) => { console.log(‘promise’); resolve();}).then(function() { console.log(’then’);});console.log(‘global’);输出: promise 、global、mutate、 then、timeout参考文章:microTask: https://github.com/kaerus-com...es-promise : https://github.com/stefanpenn… ...

December 24, 2018 · 1 min · jiezi

「今日头条」前端面试题和思路解析

一篇文章和一道面试题最近,有篇名为 《8张图帮你一步步看清 async/await 和 promise 的执行顺序》 的文章引起了我的关注。作者用一道2017年「今日头条」的前端面试题为引子,分步讲解了最终结果的执行原因。其中涉及到了不少概念,比如异步的执行顺序,宏任务,微任务等等,同时作者限定了执行范围,以浏览器的 event loop 机制为准。下面是原题的代码: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 startasync1 startasync2promise1script endpromise2async1 endsetTimeout我在看这道题的时候,先按照自己的理解写出了结果。script startasync1 startasync2promise1script endasync1 endpromise2setTimeout一些重要的概念这里需要先简单地说一些 event loop 的概念。Javascript是单线程的,所有的同步任务都会在主线程中执行。主线程之外,还有一个任务队列。每当一个异步任务有结果了,就往任务队列里塞一个事件。当主线程中的任务,都执行完之后,系统会 “依次” 读取任务队列里的事件。与之相对应的异步任务进入主线程,开始执行。异步任务之间,会存在差异,所以它们执行的优先级也会有区别。大致分为 微任务(micro task,如:Promise、MutaionObserver等)和宏任务(macro task,如:setTimeout、setInterval、I/O等)。同一次事件循环中,微任务永远在宏任务之前执行。主线程会不断重复上面的步骤,直到执行完所有任务。另外,还有 async/await 的概念。async 函数,可以理解为是Generator 函数的语法糖。它建立在promise之上,总是与await一起使用的。await会返回一个Promise 对象,或者一个表达式的值。其目的是为了让异步操作更优雅,能像同步一样地书写。我的理解再说说我对这道题的理解。首先,从console的数量上看,会输出8行结果。再瞟了一眼代码,看到了setTimeout,于是,默默地把它填入第8行。在setTimeout附近,看到了 console.log( ‘script start’ ) 和 async1(),可以确认它们是同步任务,会先在主线程中执行。所以,妥妥地在第1行填入 script start,第2行填入async1方法中的第一行 async1 start。接下来,遇到了await。从字面意思理解,让我们等等。需要等待async2()函数的返回,同时会阻塞后面的代码。所以,第3行填入 async2。讲道理,await都执行完了,该轮到console.log( ‘async1 end’ )的输出了。但是,别忘了下面还有个Promise,有一点需要注意的是:当 new 一个 Promise的时候,其 resolve 方法中的代码会立即执行。如果不是 async1()的 await 横插一杠,promise1 可以排得更前面。所以,现在第4行填入 promise1。再接下来,同步任务 console.log( ‘script end’ ) 执行。第5行填入 script end。还有第6和第7行,未填。回顾一下上面提到 async/await 的概念,其目的是为了让异步能像同步一样地书写。那么,我认为 console.log( ‘async1 end’ ) 就是个同步任务。所以,第6行填入async1 end。最后,顺理成章地在第7行填入 promise2。与作者答案的不同回过头对比与作者的答案,发现第6和第7行的顺序有问题。再耐心地往下看文章,反复地看了几遍 async1 end 和 promise2 谁先谁后,还是无法理解为何在chrome浏览器中,promise2 会先于 async1 end 输出。然后,看到评论区,发现也有人提出了相同的疑惑。@rhinel提出,在他的72.0.3622.0(正式版本)dev(64 位)的chrome中,跑出来的结果是 async1 end 在 promise2 之前。随即我想到了一种可能,JS的规范可能会在未来有变化。于是,我用自己的react工程试了一下(工程中的babel-loader版本为7.1.5。.babelrc的presets设置了stage-3),结果与我的理解一致。当前的最新版本 chromeV71,在这里的执行顺序上,的确存在有问题。于是,我也在评论区给作者留了言,进行了讨论。@rhinel最后也证实,其实最近才发布通过了这个顺序的改进方案,这篇 《Faster async functions and promises》 详细解释了这个改进,以及实现效果。不久之后,作者也在他文章的最后,补充了我们讨论的结果,供读者参考。总结最后,我想说的是,本文虽然只是由一道面试题引申出的,对浏览器执行顺序的思考、讨论与验证的过程。但正是因为有了这些过程,才让更多的思想得以碰撞,概念进一步得以理解,规范得以明了。有机会的话,希望能有与更多的同道,多多交流。PS:欢迎关注我的公众号 “超哥前端小栈”,交流更多的想法与技术。 ...

December 22, 2018 · 1 min · jiezi

细说async/await相较于Promise的优势

前言介于上一篇 「今日头条」前端面试题和思路解析 中提到的 async/await,让我想起了之前写过的一篇文章,在此做个分享。它细说了什么是async函数,以及其相较于 Promise 的优势。温故而知新,正文开始。async 函数是什么?谈及异步回调函数的嵌套,总会让人感到烦躁,特别是当业务逻辑复杂,往往需要调用几次 ajax 才能拿到所有需要的数据。从最早的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人觉得不彻底。它们都有额外的复杂性,都需要理解抽象的底层运行机制。所以,我们需要一种方法,更优雅地解决异步操作。于是,async函数出现了。一句话解释:async 函数,就是 Generator 函数的语法糖。它有以下几个特点:建立在promise之上。所以,不能把它和回调函数搭配使用。但它会声明一个异步函数,并隐式地返回一个Promise。因此可以直接return变量,无需使用Promise.resolve进行转换。和promise一样,是非阻塞的。但不用写 then 及其回调函数,这减少代码行数,也避免了代码嵌套。而且,所有异步调用,可以写在同一个代码块中,无需定义多余的中间变量。它的最大价值在于,可以使异步代码,在形式上,更接近于同步代码。它总是与await一起使用的。并且,await 只能在 async 函数体内。await 是个运算符,用于组成表达式,它会阻塞后面的代码。如果等到的是 Promise 对象,则得到其 resolve值。否则,会得到一个表达式的运算结果。为何说 async 函数是语法糖async 函数的实现,其实就是将 Generator 函数和自动执行器,包装在一个函数里。下面的这个例子,来自阮老师的 《async 函数的含义和用法》 一文。async function fn(args) { // …}// 等同于function fn(args) { return spawn(function*() { // … });}// spawn 函数就是自动执行器function spawn(genF) { return new Promise(function(resolve, reject) { var gen = genF(); function step(nextF) { try { var next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); });}所以说,async 函数是 Generator 函数的语法糖。另外,它相对较新,属于ES7中的语法。但是转码器 Babel 已经支持,转码后就能使用。async 相较于 Promise 的优势1.相比于 Promise,它能更好地处理then链function takeLongTime(n) { return new Promise(resolve => { setTimeout(() => resolve(n + 200), n); });}function step1(n) { console.log(step1 with ${n}); return takeLongTime(n);}function step2(n) { console.log(step2 with ${n}); return takeLongTime(n);}function step3(n) { console.log(step3 with ${n}); return takeLongTime(n);}现在用 Promise 方式来实现这三个步骤的处理。function doIt() { console.time(“doIt”); const time1 = 300; step1(time1) .then(time2 => step2(time2)) .then(time3 => step3(time3)) .then(result => { console.log(result is ${result}); });}doIt();// step1 with 300// step2 with 500// step3 with 700// result is 900如果用 async/await 来实现的话,会是这样:async function doIt() { console.time(“doIt”); const time1 = 300; const time2 = await step1(time1); const time3 = await step2(time2); const result = await step3(time3); console.log(result is ${result});}doIt();结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样。2.中间值现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。Pomise的实现看着很晕,传递参数太过麻烦。function doIt() { console.time(“doIt”); const time1 = 300; step1(time1) .then(time2 => { return step2(time1, time2) .then(time3 => [time1, time2, time3]); }) .then(times => { const [time1, time2, time3] = times; return step3(time1, time2, time3); }) .then(result => { console.log(result is ${result}); });}doIt();用 async/await 来写:async function doIt() { console.time(“doIt”); const time1 = 300; const time2 = await step1(time1); const time3 = await step2(time1, time2); const result = await step3(time1, time2, time3); console.log(result is ${result});}doIt();没有多余的中间值,更加优雅地实现了。3.调试相比于 Promise 更易于调试。因为没有代码块,所以不能在一个返回的箭头函数中设置断点。如果你在一个 .then 代码块中使用调试器的步进(step-over)功能,调试器并不会进入后续的 .then 代码块,因为调试器只能跟踪同步代码的每一步。现在,如果使用 async/await,你就不必再使用箭头函数。你可以对 await 语句执行步进操作,就好像他们都是普通的同步语句一样。总结JavaScript的异步编写方式,从 回调函数 到 Promise、Generator 再到 Async/Await。表面上只是写法的变化,但本质上则是语言层的一次次抽象。让我们可以用更简单的方式实现同样的功能,而不需要去考虑代码是如何执行的。换句话说就是:异步编程的最高境界,就是根本不用关心它是不是异步。参考文献async 函数的含义和用法(译) 6个Async/Await优于Promise的方面PS:欢迎关注我的公众号 “超哥前端小栈”,交流更多的想法与技术。 ...

December 22, 2018 · 2 min · jiezi

ES6-Promise对象

1 是什么先直接上图,打印一下Promise对象,观察下Promise是什么console.dir(Promise)可以知道,Promise是一个构造函数,有着reject、resolve函数。prototype有then、catch等方法,说明了只要是Promise对象都会有这两个方法。Promise构造函数是传入一个函数2 怎么用var promise = new Promise((resolve, reject) => { setTimeout(function () { console.log(‘执行完成’); resolve(‘随便什么数据’); }, 2000);});result:执行完成传入的函数有两个参数:resolve、reject,分别表示异步操作执行成功后的回调函数和异步操作失败后的回调函数。其实这里用“成功”和“失败”来描述并不准确,按照标准来讲,resolve是将Promise的状态置为fullfiled,reject是将Promise的状态置为rejected。不过在我们开始阶段可以先这么理解,后面再细究概念。运行以上代码,发现promise并没有调用,只是new了一个Promise,对应的传入函数的代码就已经执行了。因此,我们通常都是把Promise包在一个函数中,在需要的时候才去运行这个函数,如:function f() { var promise = new Promise((resolve, reject) => { setTimeout(function () { console.log(‘执行完成’); resolve(‘随便什么数据’); }, 2000); }); return promise}f()result:执行完成这时引出两个问题。为什么需要大费周章的包装这样的一个函数?resolve有什么用?2.1 为什么需要引入f().then(function(data){ console.log(data); //后面可以用传过来的数据做些其他操作 //……});在f()的返回上可以直接调用then方法,then接收一个参数,是函数,并且会拿到我们在f中调用resolve时传入的参数。运行这段代码,会在2秒后输出“执行完成”,紧接着输出“随便什么数据”。这时,我们恍然大悟了,原来then调用的function相当于我们之前写的回调函数,相当于是这种形式:function runAsync(callback){ setTimeout(function(){ console.log(‘执行完成’); callback(‘随便什么数据’); }, 2000);}runAsync(function(data){ console.log(data);});那使用Promise有什么好处呢?我们不妨考虑一个问题,如果callback函数也是一个异步操作,而且执行完后也需要有相应的回调函数,可能会变成 runAsync(function(data,function(data){}){…..})。看起来十分丑陋,假如使用Promise代码就不会这么丑陋了~Promise的优势在于,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。2.2 链式操作的用法所以,从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。所以使用Promise的正确场景是这样的:runAsync1().then(function(data){ console.log(data); return runAsync2();}).then(function(data){ console.log(data); return runAsync3();}).then(function(data){ console.log(data);});function runAsync1(){ var p = new Promise(function(resolve, reject){ //做一些异步操作 setTimeout(function(){ console.log(‘异步任务1执行完成’); resolve(‘随便什么数据1’); }, 1000); }); return p; }function runAsync2(){ var p = new Promise(function(resolve, reject){ //做一些异步操作 setTimeout(function(){ console.log(‘异步任务2执行完成’); resolve(‘随便什么数据2’); }, 2000); }); return p; }function runAsync3(){ var p = new Promise(function(resolve, reject){ //做一些异步操作 setTimeout(function(){ console.log(‘异步任务3执行完成’); resolve(‘随便什么数据3’); }, 2000); }); return p; }result:异步任务1执行完成随便什么数据1异步任务2执行完成随便什么数据2异步任务3执行完成随便什么数据3我们可以通过链式编程避免了包含多函数回调的情况,当然并不是都需要return Promise对象,你也可以返回其他数据类型,在后面的then中就能直接直接接收到数据了,比如:runAsync1().then(function(data){ console.log(data); return runAsync2();}).then(function(data){ console.log(data); return ‘直接返回数据’; //这里直接返回数据}).then(function(data){ console.log(data);});result:异步任务1执行完成随便什么数据1异步任务2执行完成随便什么数据2直接返回数据2.3 reject用法前面我们一直都是使用resolve函数,并没有使用reject函数。resolve函数是表示了“执行成功”的回调,reject函数表示“执行失败”的函数。function runAsync1() { var p = new Promise(function (resolve, reject) { //做一些异步操作 setTimeout(function () { console.log(‘异步任务1执行完成’); // resolve(‘随便什么数据1’); reject(“error”) }, 1000); }); return p;}runAsync1().then((data) => { console.log(‘resolve’); console.log(data)}, (error, data) => { console.log(‘rejected’); console.log(error)})result:异步任务1执行完成rejectederror需要注意的是,只要执行reject或者resolve,后面的就不会执行了,如:function runAsync1() { var p = new Promise(function (resolve, reject) { //做一些异步操作 setTimeout(function () { console.log(‘异步任务1执行完成’); resolve(‘随便什么数据1’); reject(“error”) }, 1000); }); return p;}因为resolve在reject的前面,所以只会执行resolve,不会执行reject。3 总结Promise是一个对象引入是为了消除多重回调函数Promise还有很多方法没有介绍(catch、finally、success、fail)参考文献ECMAScript6入门大白话讲解Promise(一)ECMAScript6入门署名 广州芦苇科技Java开发团队芦苇科技-广州专业互联网软件服务公司抓住每一处细节 ,创造每一个美好关注我们的公众号,了解更多想和我们一起奋斗吗?lagou搜索“ 芦苇科技 ”或者投放简历到 server@talkmoney.cn 加入我们吧关注我们,你的评论和点赞对我们最大的支持 ...

December 22, 2018 · 1 min · jiezi

Promise 简单实现

Promise 简单实现前言你可能知道,javascript 的任务执行的模式有两种:同步和异步。异步模式非常重要,在浏览器端,耗时很长的操作(例如 ajax 请求)都应该异步执行,避免浏览器失去响应。在异步模式编程中,我们经常使用回调函数。一不小心就可能写出以下这样的代码://事件1doSomeThing1(function() { //事件2 doSomeThing2(function() { //事件3 doSomeThing3(); });});当你的需要异步执行的函数越来越多,你的层级也会越来越深。这样的写法存在的缺点是:不利于阅读各个任务之间的高度耦合,难以维护对异常的处理比较难用 Promise 可以避免这种回调地狱,可以写成这样//事件1doSomeThing1() .then(function() { //事件2 return doSomeThing2(); }) .then(function() { //事件3 return doSomeThing3(); }) .catch(function() { //这里可以很方便的做异常处理 });在市面上有许多库都实现了 Promise,例如:Q.js 、when.js ,es6 也将 Promise 纳入了标准中es6 的 Promise 使用方法可以参考阮一峰的 http://es6.ruanyifeng.com/#do… ,我就不在做具体介绍接下来,我们模仿 ES6 的 promise,一步一步来实现一个简单版的 Promise。构造函数我们使用 Promise 的时候,const promise = new Promise((resolve, reject)=>{ // … some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); }});Promise 是一个构造函数,接收一个函数,函数里有两个参数,resolve、reject。我们可以这样子实现:class PromiseA { constructor(executor) { const resolve = value => { this.resolve(value); }; const reject = err => { this.reject(err); }; try { executor(resolve, reject); } catch (err) { reject(err); } } resolve(value) { this.resultValue = value; } reject(error) { this.resultValue = error; }}then 方法promise 中,用的最频繁的就是 then 方法,then 方法有两个参数,一个是 promise 成功时候的回调,一个是失败的回调。实现方式为:class PromiseA { constructor(executor) { const resolve = value => { this.resolve(value); }; const reject = err => { this.reject(err); }; try { executor(resolve, reject); } catch (err) { reject(err); } } resolve(value) { this.resultValue = value; if (this.fullfillCallback) { //++++++++ this.fullfillCallback(value); } } reject(error) { this.resultValue = error; if (this.rejectCallback) { //++++++++ this.rejectCallback(value); } } then(resolve, reject) { this.fullfillCallback = resolve; this.rejectCallback = resolve; }}then 方法有以下几个特性:支持链式操作每次 then 方法都是返回新的 Promise当前 promise 的状态通过返回值传递给下一个 promise错误冒泡,即如果当前 promise 没有提供 onReject 方法,会把错误冒泡到下一个 promise,方便处理then(onResolve,onReject){ //返回新的Promise并支持链式操作 return new PromiseA((resolve,reject)=>{ this.fullfillCallback = (value)=>{ try { if (onResolve) { let newValue = onResolve(value); resolve(newValue); //将当前promise执行结果,传递给下一个promise } else { resolve(value); } } catch (err) { reject(err); } } //类似fullfillCallback this.rejectCallback = (value)=>{ try { if (onReject) { let newValue = onReject(value); resolve(newValue); } else { //错误冒泡 reject(value); } } catch (err) { reject(err); } } });}这样我们就实现了一个简单版的 then 方法了加强版 then上面的实现,模拟了 then 方法的逻辑,但是还有一些缺点:then 方法只能添加一个,例如let p = new PromiseA(resolve => { setTimeout(() => { resolve(1); }, 0);});p.then(value => { console.log(’then–>’ + value);}); //无输出,因为没触发到,被后一个覆盖了p.then(value => { console.log(’then2–>’ + value);}); ////then—> 1promise 没有状态,当 promsie 在添加 then 的时候已经完成了,没法得到结果没有实现:如果上一个 promise 的返回值也是一个 Promise 对象时,则会等到这个 Promise resolve 的时候才执行下一个为了解决第一点,引入了事件监听,简单的实现如下:export default class EventEmitter { constructor() { this._events = {}; } on(type, fn, context = this) { if (!this._events[type]) { this._events[type] = []; } this._events[type].push([fn, context]); } trigger(type) { let events = this._events[type]; if (!events) { return; } let len = events.length; let eventsCopy = […events]; for (let i = 0; i < len; i++) { let event = eventsCopy[i]; let [fn, context] = event; if (fn) { fn.apply(context, [].slice.call(arguments, 1)); } } }}所以进一步对 PromiseA 进行改造:const STATUS = { PENDING: ‘pending’, FULFILLED: ‘fulfilled’, REJECTED: ‘rejected’};const EventType = { fulfill: ‘fulfill’, reject: ‘reject’};class PromiseA { constructor(executor) { //初始化事件监听及状态 this.eventEmitter = new EventEmitter(); this.status = STATUS.PENDING; const resolve = value => { if (value instanceof PromiseA) { value.then( value => { this.resolve(value); }, error => { this.reject(error); } ); } else { this.resolve(value); } }; const reject = err => { this.reject(err); }; try { executor(resolve, reject); } catch (err) { reject(err); } } resolve(value) { //增加状态 if (this.status === STATUS.PENDING) { this.status = STATUS.FULFILLED; this.resultValue = value; this.eventEmitter.trigger(EventType.fulfill, value); } } reject(error) { //增加状态 if (this.status === STATUS.PENDING) { this.status = STATUS.REJECTED; this.resultValue = error; this.eventEmitter.trigger(EventType.reject, error); } } then(onResolve, onReject) { //根据状态不同处理 if (this.status === STATUS.PENDING) { return new PromiseA((resolve, reject) => { //增加事件监听 this.eventEmitter.on(‘fulfill’, value => { try { if (onResolve) { let newValue = onResolve(value); resolve(newValue); } else { resolve(value); } } catch (err) { reject(err); } }); //增加事件监听 this.eventEmitter.on(‘reject’, value => { try { if (onReject) { let newValue = onReject(value); resolve(newValue); } else { reject(value); } } catch (err) { reject(err); } }); }); } if ( this.status === STATUS.FULFILLED || this.status === STATUS.REJECTED ) { return new PromiseA((resolve, reject) => { let callback = returnValue; if (this.status === STATUS.FULFILLED) { callback = onResolve; } if (this.status === STATUS.REJECTED) { callback = onReject; } try { let newValue = callback(this.resultValue); resolveValue(newValue, resolve, reject); } catch (err) { reject(err); } }); } }}到这里,我们的 then 方法基本就完成了。最后还有一个小知识点,就是执行时机的问题:setTimeout(function() { console.log(4);}, 0);new Promise(function(resolve) { console.log(1); resolve();}).then(function() { console.log(3);});console.log(2);//输出结果会是: 1、2、3、4promise.then,是异步的,属于 microtask,执行时机是本次事件循环结束之前,而 setTimeout 是 macrotask,执行时机是在下一次事件循环的开始之时实现这个功能,我利用了第三方库 microtask 来模拟。所以 PromiseA 修改为: resolve(value) { microtask(() => { if (this.status === STATUS.PENDING) { this.status = STATUS.FULFILLED; this.resultValue = value; this.eventEmitter.trigger(EventType.fulfill, value); } }) } reject(error) { microtask(() => { if (this.status === STATUS.PENDING) { this.status = STATUS.REJECTED; this.resultValue = error; this.eventEmitter.trigger(EventType.reject, error); } }); }到此为止,我们的 then 方法已经大功告成了。最困难的一步已经解决了catchPromise 跟普通回调的一大优势就是异常处理,我们推荐使用 Promise 的时候,总是使用 catch 来代替 then 的第二个参数。即是://badlet p = new Promise((resolve, reject) => { //…异步操作}).then( value => { //成功 }, () => { //失败 });//goodlet p = new Promise((resolve, reject) => { //…异步操作}) .then(value => { //成功 }) .catch(() => { //失败 });接下来让我们实现 catch 方法:catch(reject) { return this.then(null, reject);}哈哈~ , 你没看错。你已经实现了 catch 方法all 方法Promise.all 是一个很好用的方法。接受一个 promise 数组,然后等到所有的异步操作都完成了,就返回一个数组,包含对应的值具体实现如下:static all(promiseList = []) { //返回promise以便链式操作 return new PromiseA((resolve, reject) => { let results = []; let len = promiseList.length; let resolveCount = 0; //用于计数 let resolver = function (index, value) { resolveCount++; results[index] = value; if (resolveCount === len) { resolve(results); } }; //遍历执行所有的promise promiseList.forEach((p, i) => { if (p instanceof PromiseA) { p.then((value) => { resolver(i, value); }, (err) => { reject(err); }) } else { resolver(i, p); } }) });}race 方法race 方法为竞速,第一执行完的为准。所以只需循环一遍执行就可以了。当有第一个将 Promise 的状态改变成 fullfilled 或 reject 之后,其他的就都无效了static race(promiseList = []) { return new PromiseA((resolve, reject) => { promiseList.forEach((p, i) => { if (p instanceof PromiseA) { p.then((value) => { resolve(value); }, (err) => { reject(err); }) } else { resolve(p); } }) })}小结我们实现了一个简单版的 promise, 还有一些其他的方法在这里没有讲到。感兴趣的朋友可以自行去研究哈~附上代码完整的实现 : https://github.com/chen434202…个人博客链接:https://chen4342024.github.io… ...

December 20, 2018 · 4 min · jiezi

前端杂谈: 如何实现一个 Promise?

前端杂谈: 如何实现一个 Promise?首先, 什么是 Promise?A promise is an object that may produce a single value some time in the future: either a resolved value, or a reason that it’s not resolved (e.g., a network error occurred). A promise may be in one of 3 possible states: fulfilled, rejected, or pending. Promise users can attach callbacks to handle the fulfilled value or the reason for rejection.关键语句: Promise 是一个在将来某个时刻产生一个单一结果的对象.通俗一点来说, Promise 代表了一个值, 但是这个值我们并不确定什么时候会被返回.A promise is an object that may produce a single value some time in the future.简单看看 Promise 的历史Promise 在 1980 年代被创建出来 =>在 1988 年正式得名: Promise =>已经有很多人了解到了 Promise, 但是人们还是坚持使用 node.js 中提倡的以回调函数首个参数传 error 对象的方式处理异步代码. =>Dojo 首次大规模的使用了 Promise , 相应的 Promise/A 被提出用以规范 Promise 的实现 =>JQuery 开始使用 Promise 并真正使 Promise 广为人知 =>JQuery 没有实现部分 Promise 的功能, 这也导致了 Promie/A+ 标准的产生 =>ES6 正式引入了 Promise,并且和已有的实现了 Promise/A 规范的 library 相兼容 =>实现 Promise 之前, 让我们看看 Promise 有哪些规范Promise 是一个 thenable 对象, 也就是说 Promise 有一个 .then() 方法一个 pending 状态的 Promise 可以进入 fulfilled 和 rejected 状态promise 一旦进入 fulfilled 或 rejected 状态, 不可再改变其状态一旦 promise 改变了其状态, 它笔芯有一个值(这个值也可能是 undefined)开始实现一个 Promise首先, 让我们看看一段最普通的异步代码:// 异步方法定义var basicAsyncFunc = function(callback) { setTimeout(function() { var randomNumber = Math.random() if (randomNumber > 0.5) { callback(null, randomNumber) } else { callback(new Error(‘bad luck…’)) } }, 1000)}// 异步方法调用basicAsyncFunc((err, result) => { if (err) { console.log(the reason fail is: ${err}) return } console.log(success get result: ${result})})按照 Promise 的规范定义, 理想中 Promise 的调用方式为:// Promise 形式的异步方法定义var promiseAsyncFunc = function() {}// Promise 形式的异步方法调用promiseAsyncFunc.then( data => { console.log(success get result: ${data}) }, err => { console.log(the reason fail is: ${err}) })按照这个理想当中的调用方式, 让我们写出第一版代码.第一版 Promise:能保存回调方法// Promise 形式的异步方法定义var promiseAsyncFunc = function() { var fulfillCallback var rejectCallback setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfillCallback(randomNumber) else rejectCallback(randomNumber) }, 1000) return { then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } }}// Promise 形式的异步方法调用promiseAsyncFunc().then(fulfillCallback, rejectCallback)我们的思路是在 .then() 方法中, 将 fullfill 和 reject 结果的回调函数保存下来, 然后在异步方法中调用. 因为是异步调用, 根据 event-loop 的原理, promiseAsyncFunc().then(fulfillCallback, rejectCallback) 传入的 callback 在异步调用结束时一定是已经赋值过了.第二版 Promise:实构造函数当前我们的实现 Promise 中,异步逻辑代码和 Promise 的代码是杂糅在一起的,让我们将其区分开:var promiseAsyncFunc = function() { var fulfillCallback var rejectCallback return { fulfill: function(value) { if (fulfillCallback && typeof fulfillCallback === ‘function’) { fulfillCallback(value) } }, reject: function(err) { if (rejectCallback && typeof rejectCallback === ‘function’) { rejectCallback(err) } }, then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } }}let ownPromise = function(asyncCall) { let promise = promiseAsyncFunc() asyncCall(promise.fulfill, promise.reject) return promise}// Promise 形式的异步方法调用ownPromise(function(fulfill, reject) { setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000)})我们新定义了一个方法 ownPromise() 用于创建 Promise,并在promiseAsyncFunc() 中暴露出 fulfill 和 reject 接口方便异步代码去调用。这里有一个问题,我们在调用 ownPromise()后得到了 promise 实例,此时我们可以直接调用 fulfill(),reject()这两个方法,而理论上我们应该只应暴露 promise 的then()方法。所以我们利用闭包将这两个方法隐藏:var promiseAsyncFunc = function() { var fulfillCallback var rejectCallback return { fulfill: function(value) { if (fulfillCallback && typeof fulfillCallback === ‘function’) { fulfillCallback(value) } }, reject: function(err) { if (rejectCallback && typeof rejectCallback === ‘function’) { rejectCallback(err) } }, promise: { then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } }}let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() asyncCall(defer.fulfill, defer.reject) return defer.promise}// Promise 形式的异步方法调用ownPromise(function(fulfill, reject) { setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000)})第三版 Promise: 支持状态管理为了实现规范中对于 Promise 状态变化的要求, 我们需要为 Promise 加入状态管理, 这一步较为简单, 让我们看代码:const PENDING = Symbol(‘pending’)const FULFILLED = Symbol(‘fulfilled’)const REJECTED = Symbol(‘rejected’)// Promise 形式的异步方法定义var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback return { fulfill: function(value) { if (status !== PENDING) return if (typeof fulfillCallback === ‘function’) { fulfillCallback(value) status = FULFILLED } }, reject(error) { if (status !== PENDING) return if (typeof rejectCallback === ‘function’) { rejectCallback(error) status = REJECTED } }, promise: { then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } }}let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() asyncCall(defer.fulfill, defer.reject) return defer.promise}// Promise 形式的异步方法调用ownPromise(function(fulfill, reject) { setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000)}).then(data => console.log(data), err => console.log(err))这段代码中我们用到了 Symbol 来表示状态常量, 对 Symbol 不了解的同学可以看这里为了判断 Promise 的状态, 我们加入了 fulfill 和 reject 两个方法。并在其中判断 promise 当前状态。如果不是 pending 状态则直接 return(因为 Promise 状态只可能改变一次)。现在我们的 promise 实现了对状态控制的规范:只允许改变一次状态只能从 pending => fulfilled 或 pending => rejected但是我们的 Promise 有一个问题: promise 的值没有被保存下来。如果 promise 在异步调用完成之后才被调用 .then() 方法,则我们无法把异步调用的结果传递给回调函数。为此我们需要为 Promise 加一个 value 字段:第四版 Promise: 保存异步调用的结果我们为 promise 加入 value 字段,用于保存 Promise 的执行结果。// Promise 形式的异步方法定义var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = _value status = FULFILLED if (typeof fulfillCallback === ‘function’) { fulfillCallback(value) } }, reject(error) { if (status !== PENDING) return value = error status = REJECTED if (typeof rejectCallback === ‘function’) { rejectCallback(error) } }, promise: { then: function(_fulfillCallback, _rejectCallback) { fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } }}这里我们又发现一个问题,如果一个 Promise 已经是fulfill或reject状态。我们再调用 then() 方法时,传入的回调方法永远不会被调用(因为 status 已经不是 pending)。所以我们需要在 then()方法中对其状态进行判断:// Promise 形式的异步方法定义var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = _value status = FULFILLED if (typeof fulfillCallback === ‘function’) { fulfillCallback(value) } }, reject(error) { if (status !== PENDING) return value = error status = REJECTED if (typeof rejectCallback === ‘function’) { rejectCallback(error) } }, promise: { then: function(_fulfillCallback, _rejectCallback) { if (status === REJECTED) { _rejectCallback(value) return } if (status === FULFILLED) { _fulfillCallback(value) return } fulfillCallback = _fulfillCallback rejectCallback = _rejectCallback } } }}第五版 Promise: 支持链式调用为了支持链式调用,.then() 方法的返回值必须是用 thenable (根据 Promise/A+ 规范, .then() 方法的返回值需要是一个新的 Promise)为此我们加入一个工具方法 makeThenable()。如果传入的 value 本身就有 then()方法,则直接返回 value。否则返回一个有 then()方法的对象。在该对象的 then()方法中,我们根据 promise 的状态,调用不同的回调方法生成新的 value。function makeThenable(value, status){ if(value && typeof value.then === ‘function’){ return value } if(status === FULFILLED){ return { then: function(fulfillCallback, rejectCallback){ return makeThenable(fulfillCallback(value), FULFILLED) } } } if(status === REJECTED) { return { then: function(fulfillCallback, rejectCallback){ return makeThenable(rejectCallback(value), FULFILLED) } } }}有了以上的 makeThenable()方法,我们可以在 promise 的fulfill(),reject()回将 value 设置为 thenable:var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = makeThenable(_value, FULFILLED) // 保证当前promise的value为 thenable status = FULFILLED if (typeof fulfillCallback === ‘function’) { value.then(fulfillCallback) } }, reject(error) { if (status !== PENDING) return value = makeThenable(error, REJECTED) 、、 // 保证当前value为 thenable status = REJECTED if (typeof rejectCallback === ‘function’) { value.then(null, rejectCallback) } }, promise: { then: function(){} } }}接下来让我们看 then()方法。为了返回一个新的 promise,我们首先得创建一个新的 promise。其次当前 promise 在fulfill() 或 reject()时,应该调用新的 promise 的fullfill() 或 reject()方法。所以我们在将 fulfullCallback和rejectCallback赋值给当前 promise 时,将其包装一下。代码如下: promise: { then: function(_fulfillCallback, _rejectCallback) { let newPromiseAsyncFunc = promiseAsyncFunc() let fulfillFunc = function(value) { newPromiseAsyncFunc.fulfill(_fulfillCallback(value)) } let rejectFunc = function(err) { newPromiseAsyncFunc.fulfill(_rejectCallback(err)) } if (status === PENDING) { fulfillCallback = fulfillFunc rejectCallback = rejectFunc } else { value.then(fulfillFunc, rejectFunc) } return newPromiseAsyncFunc.promise } }如此,我们变得到了一个可以链式调用的 promise。让我们来测试一下:const PENDING = Symbol(‘pending’)const FULFILLED = Symbol(‘fulfilled’)const REJECTED = Symbol(‘rejected’)function makeThenable(value, status) { if (value && typeof value.then === ‘function’) { return value } if (status === FULFILLED) { return { then: function(fulfillCallback, rejectCallback) { return makeThenable(fulfillCallback(value), FULFILLED) } } } if (status === REJECTED) { return { then: function(fulfillCallback, rejectCallback) { return makeThenable(rejectCallback(value), FULFILLED) } } }}// Promise 形式的异步方法定义var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = makeThenable(_value, FULFILLED) status = FULFILLED if (typeof fulfillCallback === ‘function’) { value.then(fulfillCallback) } }, reject(error) { if (status !== PENDING) return value = makeThenable(error, REJECTED) status = REJECTED if (typeof rejectCallback === ‘function’) { value.then(null, rejectCallback) } }, promise: { then: function(_fulfillCallback, _rejectCallback) { let newPromiseAsyncFunc = promiseAsyncFunc() let fulfillFunc = function(value) { newPromiseAsyncFunc.fulfill(_fulfillCallback(value)) } let rejectFunc = function(err) { newPromiseAsyncFunc.fulfill(_rejectCallback(err)) } if (status === PENDING) { fulfillCallback = fulfillFunc rejectCallback = rejectFunc } else { value.then(fulfillFunc, rejectFunc) } return newPromiseAsyncFunc.promise } } }}let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() asyncCall(defer.fulfill, defer.reject) return defer.promise}let testChainedPromise = ownPromise(function(fulfill, reject) { setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000)}) .then( data => { console.log(data) return ‘return value in then1 fulfill’ }, err => { console.log(err) return ‘return value in then1 reject’ } ) .then( data => { console.log(data) return ‘return value in then2 fulfill’ }, err => { console.log(err) return ‘return value in then2 reject’ } ) .then( data => { console.log(data) }, err => { console.log(err) } )/*console output:0.9931984611850693return value in then1 fulfillreturn value in then2 fulfill/第六版 Promise: Error handling这里我们只对异步调用和fulfill 回调中抛出的 error 进行处理。首先是异步调用部分,我们将其 try catch 起来,在发生异常时调用 reject 方法,并将异常作为参数传入。let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() try { asyncCall(defer.fulfill, defer.reject) } catch (e) { defer.reject(e) } return defer.promise}然后是 fulfill 中可能出现的异常。我们对fulfillCallback(value)可能出现的异常进行捕获,并将异常传递给rejectCallback。function makeThenable(value, status) { if (value && typeof value.then === ‘function’) { return value } if (status === FULFILLED) { return { then: function(fulfillCallback, rejectCallback) { try { let newValue = fulfillCallback(value) return makeThenable(newValue, FULFILLED) } catch (e) { return makeThenable(rejectCallback(e), FULFILLED) } } } } if (status === REJECTED) { return { then: function(fulfillCallback, rejectCallback) { return makeThenable(rejectCallback(value), FULFILLED) } } }}最后让我们对完整的代码进行测试:const PENDING = Symbol(‘pending’)const FULFILLED = Symbol(‘fulfilled’)const REJECTED = Symbol(‘rejected’)function makeThenable(value, status) { if (value && typeof value.then === ‘function’) { return value } if (status === FULFILLED) { return { then: function(fulfillCallback, rejectCallback) { try { let newValue = fulfillCallback(value) return makeThenable(newValue, FULFILLED) } catch (e) { return makeThenable(rejectCallback(e), FULFILLED) } } } } if (status === REJECTED) { return { then: function(fulfillCallback, rejectCallback) { return makeThenable(rejectCallback(value), FULFILLED) } } }}// Promise 形式的异步方法定义var promiseAsyncFunc = function() { var status = PENDING var fulfillCallback var rejectCallback var value return { fulfill: function(_value) { if (status !== PENDING) return value = makeThenable(_value, FULFILLED) status = FULFILLED if (typeof fulfillCallback === ‘function’) { value.then(fulfillCallback) } }, reject(error) { if (status !== PENDING) return value = makeThenable(error, REJECTED) if (typeof rejectCallback === ‘function’) { value.then(null, rejectCallback) } status = REJECTED }, promise: { then: function(_fulfillCallback, _rejectCallback) { let newPromiseAsyncFunc = promiseAsyncFunc() let fulfillFunc = function(value) { newPromiseAsyncFunc.fulfill(_fulfillCallback(value)) } let rejectFunc = function(err) { newPromiseAsyncFunc.fulfill(_rejectCallback(err)) } if (status === PENDING) { fulfillCallback = fulfillFunc rejectCallback = rejectFunc } else { value.then(fulfillFunc, rejectFunc) } return newPromiseAsyncFunc.promise } } }}let ownPromise = function(asyncCall) { let defer = promiseAsyncFunc() try { asyncCall(defer.fulfill, defer.reject) } catch (e) { defer.reject(e) } return defer.promise}let testChainedPromise = ownPromise(function(fulfill, reject) { throw Error(‘here is an error in asyncCall’) setTimeout(() => { var randomNumber = Math.random() if (randomNumber > 0.5) fulfill(randomNumber) else reject(randomNumber) }, 1000)}) .then( data => { console.log(data) return ‘return value in then1 fulfill’ }, err => { console.log(err.message) return ‘return value in then1 reject’ } ) .then( data => { console.log(data) throw Error(‘here is an error in fulfill1’) return ‘return value in then2 fulfill’ }, err => { console.log(err.message) return ‘return value in then2 reject’ } ) .then( data => { console.log(data) }, err => { console.log(err.message) } )// console out:Error: here is an error in asyncCallreturn value in then1 rejectError: here is an error in fulfill1return value in then2 reject总结以上就是我们对于 Promise 的一个简单的实现,实现思路主要参考了 Q-A promise library for javascript。该实现的 Promise 功能较为简陋,仅实现了部分 api/规范。有任何意见和建议欢迎在评论区交流 ;)进一步阅读 && 引用对于 Promise 使用以及error handle 的讲解:https://medium.com/javascript…Promise 实现库之一: Q 对于 Promise 实现的讲解:https://github.com/kriskowal/…想了解更多 前端 和 数据可视化 ?这里是我的 前端、D3.js 、 数据可视化 的 github 地址, 欢迎 star & fork ssthouse-blog如果觉得本文不错的话, 不妨点击下面的链接关注一下 : )github 主页知乎专栏掘金想直接联系我 ?邮箱: ssthouse@163.com ...

December 13, 2018 · 9 min · jiezi

搞懂 JavsScript 异步 — 事件轮询

JavsScript 是一门单线程的编程语言,这就意味着一个时间里只能处理一件事,也就是说 JavaScript 引擎一次只能在一个线程里处理一条语句。虽然单线程简化了编程代码,因为你不必太担心并发引出的问题,这也意味着你将在阻塞主线程的情况下执行长时间的操作,如网络请求。想象一下从API请求一些数据,根据具体的情况,服务器需要一些时间来处理请求,同时阻塞主线程,使网页长时间处于无响应的状态。这就是引入异步 JavaScript 的原因。使用异步 JavaScript(如 回调函数、promise、async/await),可以不用阻塞主线程的情况下长时间执行网络请求 :)可能你知道不知道 异步 JavsScript 是如何工作,并不要紧,但知道它是如何工作,对 JavaScript 异步更深入的了解是有帮助的。所以不在啰嗦了,我们开始吧 :)同步JavaScript是如何工作的?在深入研究异步JavaScript之前,让我们首先了解同步 JavaScript 代码如何在 JavaScript 引擎中执行。例如: const second = () => { console.log(‘Hello there!’); } const first = () => { console.log(‘Hi there!’); second(); console.log(‘The End’); } first();要理解上述代码如何在 JavaScript 引擎中执行,我们必须理解执行上下文和调用堆栈(也称为执行堆栈)的概念。函数代码在函数执行上下文中执行,全局代码在全局执行上下文中执行。每个函数都有自己的执行上下文。调用栈调用堆栈顾名思义是一个具有LIFO(后进先出)结构的堆栈,用于存储在代码执行期间创建的所有执行上下文。JavaScript 只有一个调用栈,因为它是一种单线程编程语言。调用堆栈具有 LIFO 结构,这意味着项目只能从堆栈顶部添加或删除。让我们回到上面的代码片段,并尝试理解代码如何在JavaScript引擎中执行。const second = () => { console.log(‘Hello there!’);}const first = () => { console.log(‘Hi there!’); second(); console.log(‘The End’);}first();这里发生了什么?当执行此代码时,将创建一个全局执行上下文(由main()表示)并将其推到调用堆栈的顶部。当遇到对first()的调用时,它会被推送到堆栈的顶部。接下来,console.log(‘Hi there!’)被推送到堆栈的顶部,当它完成时,它会从堆栈中弹出。之后,我们调用second(),因此second()函数被推到堆栈的顶部。console.log(‘Hello there!’)被推送到堆栈顶部,并在完成时弹出堆栈。second() 函数结束,因此它从堆栈中弹出。console.log(“the End”)被推到堆栈的顶部,并在完成时删除。之后,first()函数完成,因此从堆栈中删除它。程序在这一点上完成了它的执行,所以全局执行上下文(main())从堆栈中弹出。异步JavaScript是如何工作的?现在我们已经对调用堆栈和同步JavaScript的工作原理有了基本的了解,让我们回到异步JavaScript。阻塞是什么?让我们假设我们正在以同步的方式进行图像处理或网络请求。例如:const processImage = (image) => { /** * doing some operations on image / console.log(‘Image processed’);}const networkRequest = (url) => { / * requesting network resource **/ return someData;}const greeting = () => { console.log(‘Hello World’);}processImage(logo.jpg);networkRequest(‘www.somerandomurl.com’);greeting();做图像处理和网络请求需要时间,当processImage()函数被调用时,它会根据图像的大小花费一些时间。processImage() 函数完成后,将从堆栈中删除它。然后调用 networkRequest() 函数并将其推入堆栈。同样,它也需要一些时间来完成执行。最后,当networkRequest()函数完成时,调用greeting()函数,因为它只包含一个控制台。日志语句和控制台。日志语句通常很快,因此greeting()函数立即执行并返回。因此,我们必须等待函数(如processImage()或networkRequest())完成。这意味着这些函数阻塞了调用堆栈或主线程。因此,在执行上述代码时,我们不能执行任何其他操作,这是不理想的。那么解决办法是什么呢?最简单的解决方案是异步回调。我们使用异步回调使代码非阻塞。例如:const networkRequest = () => { setTimeout(() => { console.log(‘Async Code’); }, 2000);};console.log(‘Hello World’);networkRequest();这里我使用了setTimeout方法来模拟网络请求。请记住setTimeout不是JavaScript引擎的一部分,它是web api(在浏览器中)和C/ c++ api(在node.js中)的一部分。为了理解这段代码是如何执行的,我们必须理解更多的概念,比如事件轮询和回调队列(或消息队列)。事件轮询、web api和消息队列不是JavaScript引擎的一部分,而是浏览器的JavaScript运行时环境或Nodejs JavaScript运行时环境的一部分(对于Nodejs)。在Nodejs中,web api被c/c++ api所替代。现在让我们回到上面的代码,看看它是如何异步执行的。const networkRequest = () => { setTimeout(() => { console.log(‘Async Code’); }, 2000);};console.log(‘Hello World’);networkRequest();console.log(‘The End’);当上述代码在浏览器中加载时,console.log(’ Hello World ‘) 被推送到堆栈中,并在完成后弹出堆栈。接下来,将遇到对 networkRequest() 的调用,因此将它推到堆栈的顶部。下一个 setTimeout() 函数被调用,因此它被推到堆栈的顶部。setTimeout()有两个参数:1) 回调和2) 以毫秒(ms)为单位的时间。setTimeout() 方法在web api环境中启动一个2s的计时器。此时,setTimeout()已经完成,并从堆栈中弹出。cosole.log(“the end”) 被推送到堆栈中,在完成后执行并从堆栈中删除。同时,计时器已经过期,现在回调被推送到消息队列。但是回调不会立即执行,这就是事件轮询开始的地方。事件轮询事件轮询的工作是监听调用堆栈,并确定调用堆栈是否为空。如果调用堆栈是空的,它将检查消息队列,看看是否有任何挂起的回调等待执行。在这种情况下,消息队列包含一个回调,此时调用堆栈为空。因此,事件轮询将回调推到堆栈的顶部。然后是 console.log(“Async Code”) 被推送到堆栈顶部,执行并从堆栈中弹出。此时,回调已经完成,因此从堆栈中删除它,程序最终完成。消息队列还包含来自DOM事件(如单击事件和键盘事件)的回调。例如:document.querySelector(’.btn’).addEventListener(‘click’,(event) => { console.log(‘Button Clicked’);});对于DOM事件,事件侦听器位于web api环境中,等待某个事件(在本例中单击event)发生,当该事件发生时,回调函数被放置在等待执行的消息队列中。同样,事件轮询检查调用堆栈是否为空,并在调用堆栈为空并执行回调时将事件回调推送到堆栈。延迟函数执行我们还可以使用setTimeout来延迟函数的执行,直到堆栈清空为止。例如const bar = () => { console.log(‘bar’);}const baz = () => { console.log(‘baz’);}const foo = () => { console.log(‘foo’); setTimeout(bar, 0); baz();}foo();打印结果:foobazbar当这段代码运行时,第一个函数foo()被调用,在foo内部我们调用console.log(‘foo’),然后setTimeout()被调用,bar()作为回调函数和时0秒计时器。现在,如果我们没有使用 setTimeout, bar() 函数将立即执行,但是使用 setTimeout 和0秒计时器,将bar的执行延迟到堆栈为空的时候。0秒后,bar()回调被放入等待执行的消息队列中。但是它只会在堆栈完全空的时候执行,也就是在baz和foo函数完成之后。ES6 任务队列我们已经了解了异步回调和DOM事件是如何执行的,它们使用消息队列存储等待执行所有回调。ES6引入了任务队列的概念,任务队列是 JavaScript 中的 promise 所使用的。消息队列和任务队列的区别在于,任务队列的优先级高于消息队列,这意味着任务队列中的promise 作业将在消息队列中的回调之前执行,例如:const bar = () => { console.log(‘bar’);};const baz = () => { console.log(‘baz’);};const foo = () => { console.log(‘foo’); setTimeout(bar, 0); new Promise((resolve, reject) => { resolve(‘Promise resolved’); }).then(res => console.log(res)) .catch(err => console.log(err)); baz();};foo();打印结果:foobazPromised resolvedbar我们可以看到 promise 在 setTimeout 之前执行,因为 promise 响应存储在任务队列中,任务队列的优先级高于消息队列。小结因此,我们了解了异步 JavaScript 是如何工作的,以及调用堆栈、事件循环、消息队列和任务队列等概念,这些概念共同构成了 JavaScript 运行时环境。虽然成为一名出色的JavaScript开发人员并不需要学习所有这些概念,但是了解这些概念是有帮助的:)参考:Understanding Asynchronous JavaScript — the Event Loop一个笨笨的码农,我的世界只能终身学习! ...

November 23, 2018 · 2 min · jiezi