이 기사는 es6의 Promise에 대한 간략한 분석을 제공합니다(예제 포함). 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.
Promise의 기본 사용에 대해서는 Ruan Yifeng 선생님의 "ECMAScript 6 소개"를 읽어보세요.
다른 얘기 좀 해보자.
callback
우리가 Promise에 대해 이야기할 때 보통 콜백이나 콜백 지옥으로 시작하는데, 콜백을 사용하면 어떤 나쁜 일이 일어날까요? 장소는 어디인가요?
1. 콜백 중첩
콜백을 사용하면 다음 형식으로 비즈니스 코드를 작성할 수 있습니다.
doA( function(){ doB(); doC( function(){ doD(); } ) doE(); } ); doF();#🎜 🎜#물론 이것은 단순화된 형태입니다. 조금만 생각해보면 실행 순서는 다음과 같다고 판단할 수 있습니다.
doA() doF() doB() doC() doE() doD()그러나 실제 프로젝트에서는 문제를 해결하기 위해 코드가 더 지저분해집니다. 문제가 발생하면 보기 흉한 콘텐츠를 많이 건너뛰고 기능 간에 끊임없이 이동해야 하므로 문제 해결이 기하급수적으로 더 어려워집니다. 물론 이런 문제가 발생하는 이유는 이러한 중첩 작성 방식이 선형적 사고방식에 어긋나기 때문에 실제 실행 순서를 생각하는데 더 많은 에너지를 소비해야 하기 때문입니다. 이 사고 과정에서는 단지 붉은 청어일 뿐입니다. 물론 선형적 사고 방식에 반대하는 것이 최악은 아닙니다. 실제로 위의 예와 같이 다양한 논리적 판단도 코드에 추가하게 됩니다. doC()가 완료된 후에 완료됩니다. doC()가 실행에 실패하면 어떻게 되나요? doC()를 다시 시도하시겠습니까? 아니면 다른 오류 처리 기능으로 직접 이동하시겠습니까? 이러한 판단을 프로세스에 추가하면 코드가 유지 관리 및 업데이트하기에는 너무 복잡해집니다.
2. 제어 반전
일반적으로 코드를 작성할 때는 물론 자체 코드를 제어할 수 있지만 콜백을 사용하면 , 이 콜백 함수가 실제로 계속 실행될 수 있는지 여부는 콜백을 사용하는 API에 따라 다릅니다. 예:// 回调函数是否被执行取决于 buy 模块 import {buy} from './buy.js'; buy(itemData, function(res) { console.log(res) });우리가 자주 사용하는 fetch API의 경우 일반적으로 문제가 없지만, 만약 그렇다면 타사 API를 사용하시나요? 타사 API를 호출하면 오류로 인해 여러 번 전달한 콜백 함수를 상대방이 실행하게 되나요? 이러한 문제를 방지하기 위해 콜백 함수에 판단을 추가할 수 있는데, 오류로 인해 콜백 함수가 실행되지 않는다면 어떻게 될까요?
이 콜백 함수가 때로는 동기적으로, 때로는 비동기적으로 실행된다면 어떨까요?
- 콜백 함수가 여러 번 실행됩니다 #🎜 🎜 #콜백함수가 실행되지 않습니다
- 콜백함수가 동기적으로 실행되는 경우도 있고 비동기적으로 실행되는 경우도 있습니다
- 이러한 상황에서는 콜백 함수에서 일부 처리를 해야 할 수도 있고, 콜백 함수가 실행될 때마다 일부 처리를 해야 하므로 반복되는 코드가 많이 발생합니다.
먼저 콜백 지옥의 간단한 예를 살펴보겠습니다.
이제 디렉토리에서 가장 큰 파일을 찾으려면 다음 처리 단계를 따라야 합니다.
fs.readdir
获取目录中的文件列表;循环遍历文件,使用
fs.stat
获取文件信息比较找出最大文件;
以最大文件的文件名为参数调用回调。
fs.readdir
사용 > 디렉토리에서 파일 목록을 가져옵니다. 代码为:
var fs = require('fs'); var path = require('path'); function findLargest(dir, cb) { // 读取目录下的所有文件 fs.readdir(dir, function(er, files) { if (er) return cb(er); var counter = files.length; var errored = false; var stats = []; files.forEach(function(file, index) { // 读取文件信息 fs.stat(path.join(dir, file), function(er, stat) { if (errored) return; if (er) { errored = true; return cb(er); } stats[index] = stat; // 事先算好有多少个文件,读完 1 个文件信息,计数减 1,当为 0 时,说明读取完毕,此时执行最终的比较操作 if (--counter == 0) { var largest = stats .filter(function(stat) { return stat.isFile() }) .reduce(function(prev, next) { if (prev.size > next.size) return prev return next }) cb(null, files[stats.indexOf(largest)]) } }) }) }) }
使用方式为:
// 查找当前目录最大的文件 findLargest('./', function(er, filename) { if (er) return console.error(er) console.log('largest file was:', filename) });
你可以将以上代码复制到一个比如 index.js
文件,然后执行 node index.js
就可以打印出最大的文件的名称。
看完这个例子,我们再来聊聊回调地狱的其他问题:
1.难以复用
回调的顺序确定下来之后,想对其中的某些环节进行复用也很困难,牵一发而动全身。
举个例子,如果你想对 fs.stat
fs.stat
를 사용하여 파일 정보를 가져옵니다비교하여 가장 큰 파일 찾기 콜백은 가장 큰 파일의 파일 이름을 매개변수로 호출됩니다. . #🎜🎜##🎜🎜#코드는 다음과 같습니다. #🎜🎜#
request(url, function(err, res, body) { if (err) handleError(err); fs.writeFile('1.txt', body, function(err) { request(url2, function(err, res, body) { if (err) handleError(err) }) }) });#🎜🎜#사용법은 다음과 같습니다. #🎜🎜#
request(url) .then(function(result) { return writeFileAsynv('1.txt', result) }) .then(function(result) { return request(url2) }) .catch(function(e){ handleError(e) });#🎜🎜#위 코드를 예를 들어
index.js
파일을 실행한 다음 node index.js
를 실행하여 가장 큰 파일의 이름을 출력합니다. #🎜🎜##🎜🎜#이 예제를 읽은 후 콜백 지옥의 다른 문제에 대해 이야기해 보겠습니다. #🎜🎜##🎜🎜##🎜🎜#1. 재사용하기 어려움#🎜🎜##🎜🎜##🎜 🎜 #콜백 순서가 결정된 후에는 일부 링크를 재사용하기 어려워 전체 본문에 영향을 미치게 됩니다. #🎜🎜##🎜🎜#예를 들어 fs.stat
에서 파일 정보를 읽는 데 코드를 재사용하려는 경우 외부 변수가 콜백에서 참조되기 때문에 이후에도 계속 추출됩니다. 추출. 외부 코드를 수정해야 합니다. #🎜🎜##🎜🎜##🎜🎜#2.스택 정보가 끊어졌습니다#🎜🎜##🎜🎜##🎜🎜#우리는 함수가 실행될 때 실행 컨텍스트 스택을 유지한다는 것을 알고 있습니다. will 함수를 생성하는 실행 컨텍스트가 스택에 푸시됩니다. 함수 실행이 완료되면 실행 컨텍스트가 스택에서 팝됩니다. #🎜🎜##🎜🎜#함수 A에서 함수 B가 호출되면 JavaScript는 먼저 함수 A의 실행 컨텍스트를 스택에 푸시한 다음 함수 B의 실행 컨텍스트를 스택에 푸시합니다. 함수 B의 실행 컨텍스트가 스택에서 팝됩니다. 함수 A의 실행이 완료되면 함수 A의 실행 컨텍스트가 스택에서 팝됩니다. #🎜🎜##🎜🎜#이것의 장점은 코드 실행을 중단하면 전체 스택 정보를 검색하고 여기에서 원하는 정보를 얻을 수 있다는 것입니다. #🎜🎜#可是异步回调函数并非如此,比如执行 fs.readdir
的时候,其实是将回调函数加入任务队列中,代码继续执行,直至主线程完成后,才会从任务队列中选择已经完成的任务,并将其加入栈中,此时栈中只有这一个执行上下文,如果回调报错,也无法获取调用该异步操作时的栈中的信息,不容易判定哪里出现了错误。
此外,因为是异步的缘故,使用 try catch 语句也无法直接捕获错误。
(不过 Promise 并没有解决这个问题)
3.借助外层变量
当多个异步计算同时进行,比如这里遍历读取文件信息,由于无法预期完成顺序,必须借助外层作用域的变量,比如这里的 count、errored、stats 等,不仅写起来麻烦,而且如果你忽略了文件读取错误时的情况,不记录错误状态,就会接着读取其他文件,造成无谓的浪费。此外外层的变量,也可能被其它同一作用域的函数访问并且修改,容易造成误操作。
之所以单独讲讲回调地狱,其实是想说嵌套和缩进只是回调地狱的一个梗而已,它导致的问题远非嵌套导致的可读性降低而已。
Promise
Promise 使得以上绝大部分的问题都得到了解决。
1. 嵌套问题
举个例子:
request(url, function(err, res, body) { if (err) handleError(err); fs.writeFile('1.txt', body, function(err) { request(url2, function(err, res, body) { if (err) handleError(err) }) }) });
使用 Promise 后:
request(url) .then(function(result) { return writeFileAsynv('1.txt', result) }) .then(function(result) { return request(url2) }) .catch(function(e){ handleError(e) });
而对于读取最大文件的那个例子,我们使用 promise 可以简化为:
var fs = require('fs'); var path = require('path'); var readDir = function(dir) { return new Promise(function(resolve, reject) { fs.readdir(dir, function(err, files) { if (err) reject(err); resolve(files) }) }) } var stat = function(path) { return new Promise(function(resolve, reject) { fs.stat(path, function(err, stat) { if (err) reject(err) resolve(stat) }) }) } function findLargest(dir) { return readDir(dir) .then(function(files) { let promises = files.map(file => stat(path.join(dir, file))) return Promise.all(promises).then(function(stats) { return { stats, files } }) }) .then(data => { let largest = data.stats .filter(function(stat) { return stat.isFile() }) .reduce((prev, next) => { if (prev.size > next.size) return prev return next }) return data.files[data.stats.indexOf(largest)] }) }
2. 控制反转再反转
前面我们讲到使用第三方回调 API 的时候,可能会遇到如下问题:
回调函数执行多次
回调函数没有执行
回调函数有时同步执行有时异步执行
对于第一个问题,Promise 只能 resolve 一次,剩下的调用都会被忽略。
对于第二个问题,我们可以使用 Promise.race 函数来解决:
function timeoutPromise(delay) { return new Promise( function(resolve,reject){ setTimeout( function(){ reject( "Timeout!" ); }, delay ); } ); } Promise.race( [ foo(), timeoutPromise( 3000 ) ] ) .then(function(){}, function(err){});
对于第三个问题,为什么有的时候会同步执行有的时候回异步执行呢?
我们来看个例子:
var cache = {...}; function downloadFile(url) { if(cache.has(url)) { // 如果存在cache,这里为同步调用 return Promise.resolve(cache.get(url)); } return fetch(url).then(file => cache.set(url, file)); // 这里为异步调用 } console.log('1'); getValue.then(() => console.log('2')); console.log('3');
在这个例子中,有 cahce 的情况下,打印结果为 1 2 3,在没有 cache 的时候,打印结果为 1 3 2。
然而如果将这种同步和异步混用的代码作为内部实现,只暴露接口给外部调用,调用方由于无法判断是到底是异步还是同步状态,影响程序的可维护性和可测试性。
简单来说就是同步和异步共存的情况无法保证程序逻辑的一致性。
然而 Promise 解决了这个问题,我们来看个例子:
var promise = new Promise(function (resolve){ resolve(); console.log(1); }); promise.then(function(){ console.log(2); }); console.log(3); // 1 3 2
即使 promise 对象立刻进入 resolved 状态,即同步调用 resolve 函数,then 函数中指定的方法依然是异步进行的。
PromiseA+ 规范也有明确的规定:
实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
Promise 反模式
1.Promise 嵌套
// bad loadSomething().then(function(something) { loadAnotherthing().then(function(another) { DoSomethingOnThem(something, another); }); });
// good Promise.all([loadSomething(), loadAnotherthing()]) .then(function ([something, another]) { DoSomethingOnThem(...[something, another]); });
2.断开的 Promise 链
// bad function anAsyncCall() { var promise = doSomethingAsync(); promise.then(function() { somethingComplicated(); }); return promise; }
// good function anAsyncCall() { var promise = doSomethingAsync(); return promise.then(function() { somethingComplicated() }); }
3.混乱的集合
// bad function workMyCollection(arr) { var resultArr = []; function _recursive(idx) { if (idx >= resultArr.length) return resultArr; return doSomethingAsync(arr[idx]).then(function(res) { resultArr.push(res); return _recursive(idx + 1); }); } return _recursive(0); }
你可以写成:
function workMyCollection(arr) { return Promise.all(arr.map(function(item) { return doSomethingAsync(item); })); }
如果你非要以队列的形式执行,你可以写成:
function workMyCollection(arr) { return arr.reduce(function(promise, item) { return promise.then(function(result) { return doSomethingAsyncWithResult(item, result); }); }, Promise.resolve()); }
4.catch
// bad somethingAync.then(function() { return somethingElseAsync(); }, function(err) { handleMyError(err); });
如果 somethingElseAsync 抛出错误,是无法被捕获的。你可以写成:
// good somethingAsync .then(function() { return somethingElseAsync() }) .then(null, function(err) { handleMyError(err); });
// good somethingAsync() .then(function() { return somethingElseAsync(); }) .catch(function(err) { handleMyError(err); });
红绿灯问题
题目:红灯三秒亮一次,绿灯一秒亮一次,黄灯2秒亮一次;如何让三个灯不断交替重复亮灯?(用 Promse 实现)
三个亮灯函数已经存在:
function red(){ console.log('red'); } function green(){ console.log('green'); } function yellow(){ console.log('yellow'); }
利用 then 和递归实现:
function red(){ console.log('red'); } function green(){ console.log('green'); } function yellow(){ console.log('yellow'); } var light = function(timmer, cb){ return new Promise(function(resolve, reject) { setTimeout(function() { cb(); resolve(); }, timmer); }); }; var step = function() { Promise.resolve().then(function(){ return light(3000, red); }).then(function(){ return light(2000, green); }).then(function(){ return light(1000, yellow); }).then(function(){ step(); }); } step();
promisify
有的时候,我们需要将 callback 语法的 API 改造成 Promise 语法,为此我们需要一个 promisify 的方法。
因为 callback 语法传参比较明确,最后一个参数传入回调函数,回调函数的第一个参数是一个错误信息,如果没有错误,就是 null,所以我们可以直接写出一个简单的 promisify 方法:
function promisify(original) { return function (...args) { return new Promise((resolve, reject) => { args.push(function callback(err, ...values) { if (err) { return reject(err); } return resolve(...values) }); original.call(this, ...args); }); }; }
Promise 的局限性
1. 错误被吃掉
首先我们要理解,什么是错误被吃掉,是指错误信息不被打印吗?
并不是,举个例子:
throw new Error('error'); console.log(233333);
在这种情况下,因为 throw error 的缘故,代码被阻断执行,并不会打印 233333,再举个例子:
const promise = new Promise(null); console.log(233333);
以上代码依然会被阻断执行,这是因为如果通过无效的方式使用 Promise,并且出现了一个错误阻碍了正常 Promise 的构造,结果会得到一个立刻跑出的异常,而不是一个被拒绝的 Promise。
然而再举个例子:
let promise = new Promise(() => { throw new Error('error') }); console.log(2333333);
这次会正常的打印 233333
,说明 Promise 内部的错误不会影响到 Promise 外部的代码,而这种情况我们就通常称为 “吃掉错误”。
其实这并不是 Promise 独有的局限性,try..catch 也是这样,同样会捕获一个异常并简单的吃掉错误。
而正是因为错误被吃掉,Promise 链中的错误很容易被忽略掉,这也是为什么会一般推荐在 Promise 链的最后添加一个 catch 函数,因为对于一个没有错误处理函数的 Promise 链,任何错误都会在链中被传播下去,直到你注册了错误处理函数。
2. 单一值
Promise 只能有一个完成值或一个拒绝原因,然而在真实使用的时候,往往需要传递多个值,一般做法都是构造一个对象或数组,然后再传递,then 中获得这个值后,又会进行取值赋值的操作,每次封装和解封都无疑让代码变得笨重。
说真的,并没有什么好的方法,建议是使用 ES6 的解构赋值:
Promise.all([Promise.resolve(1), Promise.resolve(2)]) .then(([x, y]) => { console.log(x, y); });
3. 无法取消
Promise 一旦新建它就会立即执行,无法中途取消。
4. 无法得知 pending 状态
当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
위 내용은 es6의 Promise에 대한 간략한 분석(예제 포함)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

C/C에서 JavaScript로 전환하려면 동적 타이핑, 쓰레기 수집 및 비동기 프로그래밍으로 적응해야합니다. 1) C/C는 수동 메모리 관리가 필요한 정적으로 입력 한 언어이며 JavaScript는 동적으로 입력하고 쓰레기 수집이 자동으로 처리됩니다. 2) C/C를 기계 코드로 컴파일 해야하는 반면 JavaScript는 해석 된 언어입니다. 3) JavaScript는 폐쇄, 프로토 타입 체인 및 약속과 같은 개념을 소개하여 유연성과 비동기 프로그래밍 기능을 향상시킵니다.

각각의 엔진의 구현 원리 및 최적화 전략이 다르기 때문에 JavaScript 엔진은 JavaScript 코드를 구문 분석하고 실행할 때 다른 영향을 미칩니다. 1. 어휘 분석 : 소스 코드를 어휘 단위로 변환합니다. 2. 문법 분석 : 추상 구문 트리를 생성합니다. 3. 최적화 및 컴파일 : JIT 컴파일러를 통해 기계 코드를 생성합니다. 4. 실행 : 기계 코드를 실행하십시오. V8 엔진은 즉각적인 컴파일 및 숨겨진 클래스를 통해 최적화하여 Spidermonkey는 유형 추론 시스템을 사용하여 동일한 코드에서 성능이 다른 성능을 제공합니다.

실제 세계에서 JavaScript의 응용 프로그램에는 서버 측 프로그래밍, 모바일 애플리케이션 개발 및 사물 인터넷 제어가 포함됩니다. 1. 서버 측 프로그래밍은 Node.js를 통해 실현되며 동시 요청 처리에 적합합니다. 2. 모바일 애플리케이션 개발은 재교육을 통해 수행되며 크로스 플랫폼 배포를 지원합니다. 3. Johnny-Five 라이브러리를 통한 IoT 장치 제어에 사용되며 하드웨어 상호 작용에 적합합니다.

일상적인 기술 도구를 사용하여 기능적 다중 테넌트 SaaS 응용 프로그램 (Edtech 앱)을 구축했으며 동일한 작업을 수행 할 수 있습니다. 먼저, 다중 테넌트 SaaS 응용 프로그램은 무엇입니까? 멀티 테넌트 SAAS 응용 프로그램은 노래에서 여러 고객에게 서비스를 제공 할 수 있습니다.

이 기사에서는 Contrim에 의해 확보 된 백엔드와의 프론트 엔드 통합을 보여 주며 Next.js를 사용하여 기능적인 Edtech SaaS 응용 프로그램을 구축합니다. Frontend는 UI 가시성을 제어하기 위해 사용자 권한을 가져오고 API가 역할 기반을 준수하도록합니다.

JavaScript는 현대 웹 개발의 핵심 언어이며 다양성과 유연성에 널리 사용됩니다. 1) 프론트 엔드 개발 : DOM 운영 및 최신 프레임 워크 (예 : React, Vue.js, Angular)를 통해 동적 웹 페이지 및 단일 페이지 응용 프로그램을 구축합니다. 2) 서버 측 개발 : Node.js는 비 차단 I/O 모델을 사용하여 높은 동시성 및 실시간 응용 프로그램을 처리합니다. 3) 모바일 및 데스크탑 애플리케이션 개발 : 크로스 플랫폼 개발은 개발 효율을 향상시키기 위해 반응 및 전자를 통해 실현됩니다.

JavaScript의 최신 트렌드에는 Typescript의 Rise, 현대 프레임 워크 및 라이브러리의 인기 및 WebAssembly의 적용이 포함됩니다. 향후 전망은보다 강력한 유형 시스템, 서버 측 JavaScript 개발, 인공 지능 및 기계 학습의 확장, IoT 및 Edge 컴퓨팅의 잠재력을 포함합니다.

JavaScript는 현대 웹 개발의 초석이며 주요 기능에는 이벤트 중심 프로그래밍, 동적 컨텐츠 생성 및 비동기 프로그래밍이 포함됩니다. 1) 이벤트 중심 프로그래밍을 사용하면 사용자 작업에 따라 웹 페이지가 동적으로 변경 될 수 있습니다. 2) 동적 컨텐츠 생성을 사용하면 조건에 따라 페이지 컨텐츠를 조정할 수 있습니다. 3) 비동기 프로그래밍은 사용자 인터페이스가 차단되지 않도록합니다. JavaScript는 웹 상호 작용, 단일 페이지 응용 프로그램 및 서버 측 개발에 널리 사용되며 사용자 경험 및 크로스 플랫폼 개발의 유연성을 크게 향상시킵니다.


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

SublimeText3 Linux 새 버전
SublimeText3 Linux 최신 버전

에디트플러스 중국어 크랙 버전
작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음

PhpStorm 맥 버전
최신(2018.2.1) 전문 PHP 통합 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기
